diff --git a/djangoldp/models.py b/djangoldp/models.py index e4670091da1a77f635927d1e5a5a30c99f4a61a6..5a9bc2d40bee53a03e61a627b885eda831a1b99e 100644 --- a/djangoldp/models.py +++ b/djangoldp/models.py @@ -9,7 +9,7 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import models from django.db.models import BinaryField, DateTimeField from django.db.models.base import ModelBase -from django.db.models.signals import post_save, pre_save +from django.db.models.signals import post_save, pre_save, pre_delete, m2m_changed from django.dispatch import receiver from django.urls import reverse_lazy, get_resolver, NoReverseMatch from django.utils.datastructures import MultiValueDictKeyError @@ -391,10 +391,11 @@ if 'djangoldp_account' not in settings.DJANGOLDP_PACKAGES: get_user_model().webid = webid -@receiver(pre_save) +@receiver([pre_save, pre_delete, m2m_changed]) def invalidate_caches(instance, **kwargs): - if isinstance(instance, Model): - from djangoldp.serializers import LDListMixin, LDPSerializer - LDPPermissions.invalidate_cache() - LDListMixin.to_representation_cache.reset() - LDPSerializer.to_representation_cache.reset() + from djangoldp.serializers import LDListMixin, LDPSerializer + LDPPermissions.invalidate_cache() + LDListMixin.to_representation_cache.reset() + + if hasattr(instance, 'urlid'): + LDPSerializer.to_representation_cache.invalidate(instance.urlid) diff --git a/djangoldp/tests/djangoldp_urls.py b/djangoldp/tests/djangoldp_urls.py index 2d43fc3dbd7715089a2a5ecd29d69bc29aa0c5cc..05d28e6fc0f28ed393673b56ea74697bb39e6c26 100644 --- a/djangoldp/tests/djangoldp_urls.py +++ b/djangoldp/tests/djangoldp_urls.py @@ -6,7 +6,7 @@ from djangoldp.views import LDPViewSet urlpatterns = [ re_path(r'^messages/', LDPViewSet.urls(model=Message, permission_classes=[LDPPermissions], fields=["@id", "text", "conversation"], nested_fields=['conversation'])), - re_path(r'^conversations/', LDPViewSet.urls(model=Conversation, nested_fields=["message_set"], permission_classes=[LDPPermissions])), + re_path(r'^conversations/', LDPViewSet.urls(model=Conversation, nested_fields=["message_set", "observers"], permission_classes=[LDPPermissions])), re_path(r'^tasks/', LDPViewSet.urls(model=Task, permission_classes=[LDPPermissions])), re_path(r'^dates/', LDPViewSet.urls(model=DateModel, permission_classes=[LDPPermissions])), re_path(r'^dummys/', LDPViewSet.urls(model=Dummy, permission_classes=[LDPPermissions], lookup_field='slug',)), diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py index b576ceb2be26134498ef956ad721048b622d0893..cc349a91256e1a95ca1f457a1578849bb27314ea 100644 --- a/djangoldp/tests/models.py +++ b/djangoldp/tests/models.py @@ -1,15 +1,12 @@ from django.conf import settings from django.contrib.auth.models import AbstractUser from django.db import models -from django.db.models import BinaryField, DateField from django.db.models.signals import post_save from django.dispatch import receiver from django.utils.datetime_safe import date -from djangoldp.fields import LDPUrlField from djangoldp.models import Model from djangoldp.permissions import LDPPermissions -from djangoldp.tests.permissions import HalfRandomPermissions class User(AbstractUser, Model): @@ -65,6 +62,7 @@ class Conversation(models.Model): author_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING) peer_user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="peers_conv", on_delete=models.DO_NOTHING) + observers = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name='observed_conversations') class Meta(Model.Meta): anonymous_perms = ['view'] diff --git a/djangoldp/tests/settings_default.py b/djangoldp/tests/settings_default.py index 2634324cb0ab0b1b711d5f985156b7d03695d69b..6773c2960879de046798131d2b78b80ffbeaf4fd 100644 --- a/djangoldp/tests/settings_default.py +++ b/djangoldp/tests/settings_default.py @@ -93,4 +93,4 @@ LDP_RDF_CONTEXT={ } SEND_BACKLINKS=False GUARDIAN_AUTO_PREFETCH = True -SERIALIZER_CACHE = False +SERIALIZER_CACHE = True diff --git a/djangoldp/tests/tests_cache.py b/djangoldp/tests/tests_cache.py index ff4aed657bae7be0b803a4b863e2add45fb52d56..5fef5e7e7a2bf5260f38a12fca9dff549c5d1912 100644 --- a/djangoldp/tests/tests_cache.py +++ b/djangoldp/tests/tests_cache.py @@ -1,11 +1,10 @@ from django.contrib.auth import get_user_model -from django.test import TestCase +from django.test import TestCase, override_settings 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 +from djangoldp.tests.models import Conversation, Project class TestCache(TestCase): @@ -22,6 +21,8 @@ class TestCache(TestCase): def tearDown(self): pass + # test container cache after new resource added + @override_settings(SERIALIZER_CACHE=True) def test_save_fk_graph_with_nested(self): response = self.client.get('/batchs/', content_type='application/ld+json') self.assertEqual(response.status_code, 200) @@ -49,9 +50,10 @@ class TestCache(TestCase): self.assertEquals(response.data['ldp:contains'][0]['title'], "title") self.assertEquals(response.data['ldp:contains'][0]['invoice']['title'], "title 2") + # test resource cache after it is updated + @override_settings(SERIALIZER_CACHE=True) def test_update_with_new_fk_relation(self): - conversation = Conversation.objects.create(author_user=self.user, - description="conversation description") + 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 = [ { @@ -71,3 +73,92 @@ class TestCache(TestCase): self.assertEquals('conversation update', response.data['description']) self.assertIn('@id', response.data['peer_user']) + # test container cache after member is deleted by view + @override_settings(SERIALIZER_CACHE=True) + def test_cached_container_deleted_resource_view(self): + conversation = Conversation.objects.create(author_user=self.user, description="conversation description") + response = self.client.get('/conversations/', content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 1) + + response = self.client.delete('/conversations/{}/'.format(conversation.pk), content_type='application/ld+json') + self.assertEqual(response.status_code, 204) + + response = self.client.get('/conversations/', content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 0) + + # test container cache after member is deleted manually + @override_settings(SERIALIZER_CACHE=True) + def test_cached_container_deleted_resource_manual(self): + conversation = Conversation.objects.create(author_user=self.user, description="conversation description") + response = self.client.get('/conversations/', content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 1) + + conversation.delete() + + response = self.client.get('/conversations/', content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 0) + + # test resource cache after it is deleted manually + @override_settings(SERIALIZER_CACHE=True) + def test_cached_resource_deleted_resource_manual(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') + self.assertEqual(response.status_code, 200) + + conversation.delete() + + response = self.client.get('/conversations/{}/'.format(conversation.pk), content_type='application/ld+json') + self.assertEqual(response.status_code, 404) + + # test container cache following m2m_changed - Project (which inherits from djangoldp.models.Model) + @override_settings(SERIALIZER_CACHE=True) + def test_cached_container_m2m_changed_project(self): + project = Project.objects.create(description='Test') + response = self.client.get('/projects/{}/team/'.format(project.pk), content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 0) + + project.team.add(self.user) + response = self.client.get('/projects/{}/team/'.format(project.pk), content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 1) + + project.team.remove(self.user) + response = self.client.get('/projects/{}/team/'.format(project.pk), content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 0) + + project.team.add(self.user) + project.team.clear() + response = self.client.get('/projects/{}/team/'.format(project.pk), content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 0) + + # test container cache following m2m_changed - Conversation (which does not inherit from djangoldp.models.Model) + @override_settings(SERIALIZER_CACHE=True) + def test_cached_container_m2m_changed_conversation(self): + conversation = Conversation.objects.create(author_user=self.user, description="conversation description") + response = self.client.get('/conversations/{}/observers/'.format(conversation.pk), content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 0) + + conversation.observers.add(self.user) + response = self.client.get('/conversations/{}/observers/'.format(conversation.pk), content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 1) + + conversation.observers.remove(self.user) + response = self.client.get('/conversations/{}/observers/'.format(conversation.pk), content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 0) + + conversation.observers.add(self.user) + conversation.observers.clear() + response = self.client.get('/conversations/{}/observers/'.format(conversation.pk), content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['ldp:contains']), 0) +