Commit 7db3e41d authored by Florent Chehab's avatar Florent Chehab
Browse files

Merge branch 'feature_moderation' into 'frontend'

Feature moderation

See merge request chehabfl/outgoing_rex!13
parents f1c99e79 1a6bb9ab
from django.db import models
from rest_framework import viewsets, permissions
from django.core.validators import MaxValueValidator
from backend.models.user import UserRestrictedModule, UserRestrictedModuleSerializer
from backend.models.user import UserRestrictedModule, UserRestrictedModuleSerializer, UserRestrictedModuleViewSet
from backend.models.user import PreviousDeparture
from django.contrib.postgres.fields import JSONField
......@@ -28,7 +27,6 @@ class PreviousDepartureFeedbackSerializer(UserRestrictedModuleSerializer):
fields = '__all__'
class PreviousDepartureFeedbackViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.DjangoModelPermissions,)
class PreviousDepartureFeedbackViewSet(UserRestrictedModuleViewSet):
queryset = PreviousDepartureFeedback.objects.all() # pylint: disable=E1101
serializer_class = PreviousDepartureFeedbackSerializer
from django.db import models
from rest_framework import viewsets, permissions
from rest_framework import permissions
from django.core.validators import MaxValueValidator
from backend.models.university import University
from backend.models.user import UserRestrictedModule, UserRestrictedModuleSerializer
from backend.models.user import UserRestrictedModule, UserRestrictedModuleSerializer, UserRestrictedModuleViewSet
from backend.models.otherCore import Specialty
from backend.models.user import RecommendationList
......@@ -30,7 +30,7 @@ class RecommendationSerializer(UserRestrictedModuleSerializer):
fields = '__all__'
class RecommendationViewSet(viewsets.ModelViewSet):
class RecommendationViewSet(UserRestrictedModuleViewSet):
permission_classes = (permissions.DjangoObjectPermissions,)
queryset = Recommendation.objects.all() # pylint: disable=E1101
serializer_class = RecommendationSerializer
......
from django.db import models
from rest_framework import viewsets, permissions
from backend.models.user import UserRestrictedModule, UserRestrictedModuleSerializer
from backend.models.user import UserRestrictedModule, UserRestrictedModuleSerializer, UserRestrictedModuleViewSet
class RecommendationList(UserRestrictedModule):
......@@ -8,7 +7,7 @@ class RecommendationList(UserRestrictedModule):
title = models.CharField(max_length=200)
class Meta:
unique_together = ('title', 'user')
unique_together = ('title', 'owner')
class RecommendationListSerializer(UserRestrictedModuleSerializer):
......@@ -18,8 +17,7 @@ class RecommendationListSerializer(UserRestrictedModuleSerializer):
fields = '__all__'
class RecommendationListViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.DjangoModelPermissions,)
class RecommendationListViewSet(UserRestrictedModuleViewSet):
queryset = RecommendationList.objects.all() # pylint: disable=E1101
serializer_class = RecommendationListSerializer
# TODO add public support to check access rights
from django.db import models
from rest_framework import viewsets, permissions
from rest_framework import permissions
from backend.models.tools import UsefullLinksField
from django.contrib.postgres.fields import JSONField
from backend.models.user import UserRestrictedModule, UserRestrictedModuleSerializer
from backend.models.user import UserRestrictedModule, UserRestrictedModuleSerializer, UserRestrictedModuleViewSet
class UserData(UserRestrictedModule):
......@@ -19,7 +19,7 @@ class UserDataSerializer(UserRestrictedModuleSerializer):
fields = '__all__'
class UserDataViewSet(viewsets.ModelViewSet):
class UserDataViewSet(UserRestrictedModuleViewSet):
permission_classes = (permissions.IsAdminUser,) # TODO change
queryset = UserData.objects.all() # pylint: disable=E1101
serializer_class = UserDataSerializer
......
from django.db import models
from rest_framework import serializers
from django.contrib.auth.models import User
from backend.models.my_model import MyModel, MyModelSerializer, MyModelViewSet
class UserRestrictedModule(models.Model):
class UserRestrictedModule(MyModel):
# RGPD made easy with CASCADE
user = models.ForeignKey(User, on_delete=models.CASCADE)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
# User is anonymised
is_anonymous = models.BooleanField(default=True)
......@@ -17,11 +18,15 @@ class UserRestrictedModule(models.Model):
abstract = True
class UserRestrictedModuleSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(
class UserRestrictedModuleSerializer(MyModelSerializer):
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault(),
) # TODO check that this works
class Meta:
model = UserRestrictedModule
fields = '__all__'
class UserRestrictedModuleViewSet(MyModelViewSet):
serializer_class = UserRestrictedModuleSerializer
......@@ -7,6 +7,8 @@ new_revision_saved = django.dispatch.Signal(providing_args=["obj"])
def squashRevisionByUser(sender, obj, **kwargs):
"""
TODO add comment and unit test
It should also work with moderation as obj will be a versionned object
"""
versions = Version.objects\
.get_for_object(obj)\
......
# from django.test import TestCase
# Create your tests here.
from .withUserTestCase import WithUserTestCase # noqa: F401
from django.test import override_settings
from .withUserTestCase import WithUserTestCase
from backend.models.my_model import ForTestingModeration
from backend.models.my_model import PendingModeration
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
class ModerationTestCase(WithUserTestCase):
@classmethod
def setUpMoreTestData(cls):
cls.api_moderation = '/api/test/moderation/'
def test_setting_ok(self):
self.assertTrue(settings.TESTING)
#####
def _submit_post_test(self, client, data):
response = client.post(
self.api_moderation,
data,
format='json'
)
self.assertEqual(response.status_code, 201)
return response
def _submit_put_test(self, client, data, pk):
response = client.put(
self.api_moderation + str(pk) + '/',
data,
format='json'
)
self.assertEqual(response.status_code, 200)
return response
def _test_retreive_instance(self, data):
matching = ForTestingModeration.objects.filter(
aaa=data['aaa']
)
self.assertTrue(matching.exists())
return matching[0]
def _test_retreive_instance_in_moderation(self, instance, expected_fail=False):
ct = ContentType.objects.get_for_model(ForTestingModeration)
pending = PendingModeration.objects.filter(
content_type=ct, object_id=instance.pk)
if expected_fail:
self.assertFalse(pending.exists())
return None
else:
self.assertTrue(pending.exists())
self.assertEqual(len(pending), 1)
return pending[0]
def _test_val_null(self, val1, null):
if null:
self.assertIsNone(val1)
else:
self.assertIsNotNone(val1)
def _test_moderated_val(self, instance, null=True):
self._test_val_null(instance.moderated_by, null)
self._test_val_null(instance.moderated_on, null)
def _test_updated_val(self, instance, null=True):
self._test_val_null(instance.updated_on, null)
self._test_val_null(instance.updated_by, null)
def _test_new_obj_val(self, instance, data):
self.assertEqual(
instance.new_object['aaa'],
data['aaa']
)
####
@override_settings(MODERATION_ACTIVATED=True)
def test_moderation_activated(self):
"""
Test to check that when moderation IS activated,
the app behaves as expected regarding creation
and moderation of models.
"""
# phase 1
# Need moderation
data_1 = {'aaa': "Test"}
self._submit_post_test(self.authenticated_client, data_1)
instance = self._test_retreive_instance(data_1)
self._test_updated_val(instance, null=True)
self._test_moderated_val(instance, null=True)
instance_in_moderation = self._test_retreive_instance_in_moderation(
instance)
self._test_updated_val(instance_in_moderation, null=False)
self._test_new_obj_val(instance_in_moderation, data_1)
# Phase 2
# More moderation
data_2 = {'aaa': "Test 2"}
self._submit_put_test(self.authenticated_client, data_2, instance.pk)
instance = self._test_retreive_instance(data_1) # not data_2 !
self._test_updated_val(instance, null=True)
self._test_moderated_val(instance, null=True)
instance_in_moderation = self._test_retreive_instance_in_moderation(
instance)
self._test_updated_val(instance_in_moderation, null=False)
# here we espect data_2
self._test_new_obj_val(instance_in_moderation, data_2)
# Phase 3
# Moderation with modification
data_3 = {'aaa': "Test 3"}
self._submit_put_test(self.moderator_client, data_3, instance.pk)
instance = self._test_retreive_instance(data_3) # not data_2 !
self._test_updated_val(instance, null=False)
self._test_moderated_val(instance, null=False)
self.assertEqual(instance.updated_by, self.moderator_user)
# (below) because the field aaa was updated by the moderator
self.assertEqual(instance.moderated_by, self.moderator_user)
instance_in_moderation = self._test_retreive_instance_in_moderation(
instance, True)
# Phase 4
# Put that require moderation
data_4 = {'aaa': "Test 4"}
self._submit_put_test(self.authenticated_client, data_4, instance.pk)
instance = self._test_retreive_instance(data_3) # not data_4 !
self._test_updated_val(instance, null=False) # Not True
self._test_moderated_val(instance, null=False) # Not True
instance_in_moderation = self._test_retreive_instance_in_moderation(
instance)
self._test_updated_val(instance_in_moderation, null=False)
# here we espect data_4
self. _test_new_obj_val(instance_in_moderation, data_4)
# Phase 5
# Moderation with no modification
self._submit_put_test(self.moderator_client, data_4, instance.pk)
instance = self._test_retreive_instance(data_4) # not data_2 !
self._test_updated_val(instance, null=False)
self._test_moderated_val(instance, null=False)
# (below) because the field aaa was NOT updated by the moderator
self.assertEqual(instance.updated_by, self.authenticated_user)
self.assertEqual(instance.moderated_by, self.moderator_user)
instance_in_moderation = self._test_retreive_instance_in_moderation(
instance, True)
####
@override_settings(MODERATION_ACTIVATED=True)
def test_staff_no_moderation(self):
"""
Test to check that staff member are not subject to moderation.
"""
data_1 = {'aaa': "Test sdfdf"}
self._submit_post_test(self.staff_client, data_1)
instance = self._test_retreive_instance(data_1)
self._test_updated_val(instance, null=False)
self._test_moderated_val(instance, null=False)
self.assertEqual(instance.updated_by, self.staff_user)
self.assertEqual(instance.moderated_by, self.staff_user)
self._test_retreive_instance_in_moderation(
instance, expected_fail=True
)
####
@override_settings(MODERATION_ACTIVATED=False)
def test_moderation_not_activated(self):
"""
Test to check that when moderation IS NOT activated,
the app behaves as expected regarding creation
and moderation of models.
"""
# phase 1
# Test that an authentificated user can post data
# And that there is nothing pending in moderation
data_1 = {'aaa': "Test"}
self._submit_post_test(self.authenticated_client, data_1)
instance = self._test_retreive_instance(data_1)
self._test_updated_val(instance, null=False)
self._test_moderated_val(instance, null=False)
self.assertEqual(instance.updated_by, self.authenticated_user)
self.assertEqual(instance.moderated_by, self.authenticated_user)
self._test_retreive_instance_in_moderation(
instance, expected_fail=True
)
from .withUserTestCase import WithUserTestCase
class ReadAccessTestCase(WithUserTestCase):
@classmethod
def setUpMoreTestData(cls):
cls.api_moderation = '/api/test/moderation/'
def test_read(self):
"""
Basic tests to check read access rights
On MyModelViewset
"""
response = self.staff_client.get(self.api_moderation)
self.assertEqual(response.status_code, 200)
response = self.moderator_client.get(self.api_moderation)
self.assertEqual(response.status_code, 200)
response = self.authenticated_client.get(self.api_moderation)
self.assertEqual(response.status_code, 200)
response = self.unauthenticated_client.get(self.api_moderation)
self.assertEqual(response.status_code, 403)
from backend.models.my_model import ForTestingVersionning
from reversion.models import Version
from backend.signals import squashRevisionByUser
import reversion
from .withUserTestCase import WithUserTestCase
import json
class SquashVersionsTestCase(WithUserTestCase):
def setUp(self):
self.obj = ForTestingVersionning(bbb="v1")
with reversion.create_revision():
self.obj.save()
reversion.set_user(self.authenticated_user)
def get_versions(self, obj):
return Version.objects\
.get_for_object(obj)\
.select_related('revision')\
.order_by('-revision__date_created')
def get_version_data(self, version):
return json.loads(version.serialized_data)[0]['fields']
def test_no_squashing(self):
"""
Test to check that when two different users successively
Modify a model, no squashing is performed
"""
with reversion.create_revision():
self.obj.bbb = "v2 other user"
self.obj.save()
reversion.set_user(self.authenticated_user_2)
squashRevisionByUser(None, self.obj)
versions = self.get_versions(self.obj)
self.assertEqual(len(versions), 2)
first_edit = self.get_version_data(versions[1])
second_edit = self.get_version_data(versions[0])
self.assertEqual(first_edit['bbb'], "v1")
self.assertEqual(second_edit['bbb'], "v2 other user")
def test_squashing(self):
"""
Test to check that when a user save two different
versions of a same model, no squashing is performed
"""
with reversion.create_revision():
self.obj.bbb = "v2"
self.obj.save()
reversion.set_user(self.authenticated_user)
squashRevisionByUser(None, self.obj)
versions = self.get_versions(self.obj)
self.assertEqual(len(versions), 1)
version_data = self.get_version_data(versions[0])
self.assertEqual(version_data['bbb'], "v2")
from .withUserTestCase import WithUserTestCase
from backend.models.my_model import ForTestingVersionning
from django.conf import settings
from reversion.models import Version
from backend.signals import new_revision_saved
class VersionningTestCase(WithUserTestCase):
@classmethod
def setUpMoreTestData(cls):
cls.testing_model = ForTestingVersionning
cls.api_versionning = '/api/test/versionning/'
def reset_signal_called(self):
self.signal_was_called = False
def test_setting_ok(self):
self.assertTrue(settings.TESTING)
#####
def _submit_put_test(self, client, data, pk):
response = client.put(
self.api_versionning + str(pk) + '/',
data,
format='json'
)
self.assertEqual(response.status_code, 200)
return response
def _test_retreive_instance(self, data):
matching = self.testing_model.objects.filter(
bbb=data['bbb']
)
self.assertTrue(matching.exists())
return matching[0]
####
def test_versionning(self):
"""
Test to check that versionning is working
We also check that new_revision_saved is called
"""
def _test_signal_sent(sender, obj, **kwargs):
self.signal_was_called = True
new_revision_saved.connect(_test_signal_sent)
data_1 = {'bbb': "Test 1"}
response = self.authenticated_client.post(
self.api_versionning,
data_1,
format='json'
)
self.assertEqual(response.status_code, 201)
instance = self._test_retreive_instance(data_1)
versions = Version.objects.get_for_object(instance)
self.assertEqual(len(versions), 1)
self.assertTrue(self.signal_was_called)
self.reset_signal_called()
data_2 = {'bbb': "Test 2"}
response = self.authenticated_client_2.put(
self.api_versionning + str(instance.pk) + '/',
data_2,
format='json'
)
self.assertEqual(response.status_code, 200)
versions = Version.objects.get_for_object(instance)
self.assertEqual(len(versions), 2)
self.assertTrue(self.signal_was_called)
from rest_framework.test import APIClient
from django.contrib.auth.models import User
from django.contrib.auth.models import Group
from django.test import TestCase
class WithUserTestCase(TestCase):
@classmethod
def setUpTestData(cls):
password = '123456'
cls.staff_user = User.objects.create_user(
username='staff_member',
email='master@master.fr',
password=password
)
cls.staff_user.is_staff = True
cls.staff_user.save()
cls.moderator_user = User.objects.create_user(
username='moderator_member',
email='moderator@moderator.fr',
password=password
)
cls.moderator_group = Group.objects.get_or_create(
name='Moderators'
)[0]
cls.moderator_group.user_set.add(cls.moderator_user)
cls.moderator_group.save()
cls.authenticated_user = User.objects.create_user(
username='authenticated_user',
email='authenticated@authenticated.fr',
password=password
)
cls.authenticated_user_2 = User.objects.create_user(
username='authenticated_user_2',
email='authenticated_2@authenticated.fr',
password=password
)
cls.staff_client = APIClient()
cls.staff_client.login(
username=cls.staff_user.username,
password=password
)
cls.moderator_client = APIClient()
cls.moderator_client.login(
username=cls.moderator_user.username,
password=password
)
cls.authenticated_client = APIClient()
cls.authenticated_client.login(
username=cls.authenticated_user.username,
password=password
)
cls.authenticated_client_2 = APIClient()
cls.authenticated_client_2.login(
username=cls.authenticated_user_2.username,
password=password
)
cls.unauthenticated_client = APIClient()
cls.setUpMoreTestData()
@classmethod
def setUpMoreTestData(cls):
pass
from django.conf.urls import url, include
from django.conf import settings
from rest_framework import routers
from backend.models.location import CountryViewSet
from backend.models.location import CityViewSet
......@@ -50,6 +52,8 @@ from backend.models.user import PreviousDepartureViewSet
from backend.models.user import PreviousDepartureFeedbackViewSet
from backend.models.user import UserDataViewSet
from backend.models.my_model import PendingModerationViewSet
from rest_framework.documentation import include_docs_urls
......@@ -60,6 +64,13 @@ urlpatterns = [