diff --git a/djangoldp/permissions.py b/djangoldp/permissions.py index 1d26706bfde5c8999bd8f7cd1bb6cc83d5754c05..bfbd3e94f3972cb1640d88d455c4dc9bda9e001f 100644 --- a/djangoldp/permissions.py +++ b/djangoldp/permissions.py @@ -30,7 +30,7 @@ class LDPPermissions(DjangoObjectPermissions): @classmethod def refresh_cache(cls): - if (time.time() - cls.perms_cache['time']) > 100: + if (time.time() - cls.perms_cache['time']) > 5: cls.invalidate_cache() def user_permissions(self, user, obj_or_model, obj=None): diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py index 4d320001366f9109cd33a9e5913d8ea4c10c5c6e..3dad3bed765ea96d94d6ecf01faff2ba4ebecbd6 100644 --- a/djangoldp/serializers.py +++ b/djangoldp/serializers.py @@ -1,24 +1,25 @@ -import time import uuid from collections import OrderedDict, Mapping, Iterable from typing import Any from urllib import parse +import time from django.conf import settings from django.contrib.auth import get_user_model from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ValidationError as DjangoValidationError from django.db import transaction +from django.db.models import QuerySet from django.urls import resolve, Resolver404, get_script_prefix from django.urls.resolvers import get_resolver -from django.db.models import QuerySet from django.utils.datastructures import MultiValueDictKeyError from django.utils.encoding import uri_to_iri from rest_framework.exceptions import ValidationError from rest_framework.fields import SkipField, empty, ReadOnlyField from rest_framework.fields import get_error_detail, set_value from rest_framework.relations import HyperlinkedRelatedField, ManyRelatedField, MANY_RELATION_KWARGS, Hyperlink -from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer, ModelSerializer, LIST_SERIALIZER_KWARGS +from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer, ModelSerializer, \ + LIST_SERIALIZER_KWARGS from rest_framework.settings import api_settings from rest_framework.utils import model_meta from rest_framework.utils.field_mapping import get_nested_relation_kwargs @@ -31,28 +32,31 @@ from djangoldp.permissions import LDPPermissions class InMemoryCache: - def __init__(self): + def __init__(self, max_age=300): + self.max_age = max_age self.cache = { - 'time': time.time() } - def invalidate_cache(self): + def reset(self): self.cache = { - 'time': time.time() } - def refresh_cache(self): - if (time.time() - self.cache['time']) > 300: - self.invalidate_cache() - def has(self, cache_key): - return cache_key in self.cache + if cache_key in self.cache: + if time.time() - self.cache[cache_key]['time'] < self.max_age: + return True + else: + self.invalidate(cache_key) + return False def get(self, cache_key): - return self.cache[cache_key] + return self.cache[cache_key]['value'] def set(self, cache_key, value): - self.cache[cache_key] = value + self.cache[cache_key] = {'time': time.time(), 'value': value} + + def invalidate(self, cache_key): + self.cache.pop(cache_key, None) class LDListMixin: @@ -88,7 +92,6 @@ class LDListMixin: - Can Add if add permission on contained object's type - Can view the container is view permission on container model : container obj are filtered by view permission ''' - self.to_representation_cache.refresh_cache() try: child_model = getattr(self, self.child_attr).Meta.model except AttributeError: @@ -133,10 +136,10 @@ class LDListMixin: Model.get_permissions(parent_model, self.context, ['view'])) self.to_representation_cache.set(self.id, {'@id': self.id, - '@type': 'ldp:Container', - 'ldp:contains': super().to_representation(filtered_values), - 'permissions': container_permissions - }) + '@type': 'ldp:Container', + 'ldp:contains': super().to_representation(filtered_values), + 'permissions': container_permissions + }) return self.to_representation_cache.get(self.id) @@ -262,6 +265,7 @@ class JsonLdRelatedField(JsonLdField): class JsonLdIdentityField(JsonLdField): '''Represents an identity (url) field for a serializer''' + def __init__(self, view_name=None, **kwargs): kwargs['read_only'] = True kwargs['source'] = '*' @@ -317,7 +321,6 @@ class LDPSerializer(HyperlinkedModelSerializer): def to_representation(self, obj): # external Models should only be returned with an id (on GET) - self.to_representation_cache.refresh_cache() if self.context['request'].method == 'GET' and Model.is_external(obj): return {'@id': obj.urlid} @@ -591,6 +594,7 @@ class LDPSerializer(HyperlinkedModelSerializer): def create(self, validated_data): with transaction.atomic(): instance = self.internal_create(validated_data, model=self.Meta.model) + LDListMixin.to_representation_cache.invalidate('{}{}'.format(settings.BASE_URL, Model.resource(self.Meta.model))) self.attach_related_object(instance, validated_data) return instance @@ -660,6 +664,11 @@ class LDPSerializer(HyperlinkedModelSerializer): self.save_or_update_nested_list(instance, nested_fields) instance.save() + resource_path = Model.resource(self.Meta.model) + LDListMixin.to_representation_cache.invalidate('{}{}'.format(settings.BASE_URL, resource_path)) + LDPSerializer.to_representation_cache.invalidate('{}{}'.format(settings.BASE_URL, resource_path)) + LDListMixin.to_representation_cache.invalidate('{}{}{}'.format(settings.BASE_URL, resource_path, instance.id)) + LDPSerializer.to_representation_cache.invalidate('{}{}{}'.format(settings.BASE_URL, resource_path, instance.id)) return instance @@ -710,7 +719,7 @@ class LDPSerializer(HyperlinkedModelSerializer): info = model_meta.get_field_info(instance) slug_field = Model.slug_field(instance) relation_info = info.relations.get(attr) - if slug_field in value : + if slug_field in value: value = self.update_dict_value_when_id_is_provided(attr, instance, relation_info, slug_field, value) else: if 'urlid' in value: diff --git a/djangoldp/tests/perf_result.csv b/djangoldp/tests/perf_result.csv index 33d2f033490923f81080c5ac54527b537eb97bb3..c6281b8e0e6b9326faa9c8451eaf3f71d72037b9 100644 --- a/djangoldp/tests/perf_result.csv +++ b/djangoldp/tests/perf_result.csv @@ -46,4 +46,6 @@ jbl+AC0-T440p,Oct 09 2020 11:56:19,True,True,100,0.003119325637817,0.00560247182 jbl+AC0-T440p,Oct 09 2020 11:58:22,True,True,100,0.003008058071136,0.005401248931885,0.010658957958222,0.003909242153168,0.000718443393707,0.301162958145142,TRUE,3 jbl+AC0-T440p,Oct 09 2020 11:59:16,True,True,100,0.003015418052673,0.005526115894318,0.010740044116974,0.00400491476059,0.000724492073059,0.313828229904175,TRUE,4 jbl+AC0-T440p,Oct 09 2020 12:00:32,True,True,100,0.002969658374786,0.005434756278992,0.018136837482452,0.003030817508698,0.000726938247681,0.320115327835083,TRUE,0 -jbl-T440p,Oct 09 2020 12:21:00,True,True,100,0.0034934663772583007,0.0061032938957214355,0.019232537746429443,0.003091294765472412,0.0007375502586364747,0.36986708641052246,N/A +jbl+AC0-T440p,Oct 09 2020 12:21:00,True,True,100,0.003493466377258,0.006103293895721,0.01923253774643,0.003091294765472,0.000737550258636,0.369867086410522,TRUE,0 +jbl+AC0-T440p,Oct 15 2020 22:00:10,True,True,100,0.003004941940308,0.00546817779541,0.018348352909088,0.003068554401398,0.000729415416718,0.320573329925537,TRUE,0 +jbl+AC0-T440p,Oct 15 2020 22:15:26,True,True,100,0.003350086212158,0.005898218154907,0.011625332832337,0.004264788627625,0.000795011520386,0.319289922714233,TRUE,1 diff --git a/djangoldp/tests/runner.py b/djangoldp/tests/runner.py index 3966fd5190de0a9f37e7c3838f15e24b2c234e62..7113ac4be827069510bf0aa2fccec5f19b11c134 100644 --- a/djangoldp/tests/runner.py +++ b/djangoldp/tests/runner.py @@ -24,7 +24,8 @@ failures = test_runner.run_tests([ 'djangoldp.tests.tests_sources', 'djangoldp.tests.tests_pagination', 'djangoldp.tests.tests_inbox', - 'djangoldp.tests.tests_backlinks_service' + 'djangoldp.tests.tests_backlinks_service', + 'djangoldp.tests.tests_cache' ]) if failures: diff --git a/djangoldp/tests/tests_cache.py b/djangoldp/tests/tests_cache.py new file mode 100644 index 0000000000000000000000000000000000000000..ff4aed657bae7be0b803a4b863e2add45fb52d56 --- /dev/null +++ b/djangoldp/tests/tests_cache.py @@ -0,0 +1,73 @@ +from django.contrib.auth import get_user_model +from django.test import TestCase +from rest_framework.test import APIRequestFactory, APIClient +from rest_framework.utils import json + +from djangoldp.models import Model +from djangoldp.serializers import LDPSerializer, LDListMixin +from djangoldp.tests.models import Skill, JobOffer, Invoice, LDPDummy, Resource, Post, Circle, Project, Conversation + + +class TestCache(TestCase): + + def setUp(self): + self.factory = APIRequestFactory() + self.client = APIClient() + self.user = get_user_model().objects.create_user(username='john', email='jlennon@beatles.com', + password='glass onion') + self.client.force_authenticate(self.user) + LDListMixin.to_representation_cache.reset() + LDPSerializer.to_representation_cache.reset() + + def tearDown(self): + pass + + def test_save_fk_graph_with_nested(self): + response = self.client.get('/batchs/', content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + + post = { + '@graph': [ + { + 'http://happy-dev.fr/owl/#title': "title", + 'http://happy-dev.fr/owl/#invoice': { + '@id': "_.123" + } + }, + { + '@id': "_.123", + 'http://happy-dev.fr/owl/#title': "title 2" + } + ] + } + + response = self.client.post('/batchs/', data=json.dumps(post), content_type='application/ld+json') + self.assertEqual(response.status_code, 201) + + response = self.client.get('/batchs/', content_type='application/ld+json') + self.assertIn('ldp:contains', response.data) + self.assertEquals(response.data['ldp:contains'][0]['title'], "title") + self.assertEquals(response.data['ldp:contains'][0]['invoice']['title'], "title 2") + + def test_update_with_new_fk_relation(self): + conversation = Conversation.objects.create(author_user=self.user, + description="conversation description") + response = self.client.get('/conversations/{}/'.format(conversation.pk), content_type='application/ld+json') + body = [ + { + '@id': "/conversations/{}/".format(conversation.pk), + 'http://happy-dev.fr/owl/#description': "conversation update", + 'http://happy-dev.fr/owl/#peer_user': { + '@id': 'http://happy-dev.fr/users/{}'.format(self.user.pk), + } + } + ] + response = self.client.put('/conversations/{}/'.format(conversation.pk), data=json.dumps(body), + content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + + response = self.client.get('/conversations/{}/'.format(conversation.pk), content_type='application/ld+json') + self.assertIn('peer_user', response.data) + self.assertEquals('conversation update', response.data['description']) + self.assertIn('@id', response.data['peer_user']) + diff --git a/djangoldp/tests/tests_get.py b/djangoldp/tests/tests_get.py index 5e93f212ee7411bb9e1262674a1a4c9b240f8828..be8e5b19179f36fe7d728333e74d136ddcf040aa 100644 --- a/djangoldp/tests/tests_get.py +++ b/djangoldp/tests/tests_get.py @@ -11,8 +11,8 @@ class TestGET(APITestCase): def setUp(self): self.factory = APIRequestFactory() self.client = APIClient() - LDListMixin.to_representation_cache.invalidate_cache() - LDPSerializer.to_representation_cache.invalidate_cache() + LDListMixin.to_representation_cache.reset() + LDPSerializer.to_representation_cache.reset() def tearDown(self): pass diff --git a/djangoldp/tests/tests_save.py b/djangoldp/tests/tests_save.py index 7a7dbeb3b9059be91bde8ae3127c5a48a6dfa63c..afd624e4e118a1b8489430b515c2e28e5fa3e0b7 100644 --- a/djangoldp/tests/tests_save.py +++ b/djangoldp/tests/tests_save.py @@ -16,8 +16,8 @@ class Save(TestCase): self.user = get_user_model().objects.create_user(username='john', email='jlennon@beatles.com', password='glass onion') self.client.force_authenticate(self.user) - LDListMixin.to_representation_cache.invalidate_cache() - LDPSerializer.to_representation_cache.invalidate_cache() + LDListMixin.to_representation_cache.reset() + LDPSerializer.to_representation_cache.reset() def tearDown(self): pass diff --git a/djangoldp/tests/tests_update.py b/djangoldp/tests/tests_update.py index ea293c3a4118af7889fcca3224e71e66002b88a7..a4b6ef01f8d0351be43322f36c9e1badba4dc873 100644 --- a/djangoldp/tests/tests_update.py +++ b/djangoldp/tests/tests_update.py @@ -17,8 +17,8 @@ class Update(TestCase): self.user = get_user_model().objects.create_user(username='john', email='jlennon@beatles.com', password='glass onion') self.client.force_authenticate(user=self.user) - LDListMixin.to_representation_cache.invalidate_cache() - LDPSerializer.to_representation_cache.invalidate_cache() + LDListMixin.to_representation_cache.reset() + LDPSerializer.to_representation_cache.reset() def tearDown(self): pass