Commit 6b656ae7 authored by Florent Chehab's avatar Florent Chehab

Merge branch 'versionning_and_model_update' into 'master'

Versionning and moderation update

See merge request chehabfl/outgoing_rex!20
parents aaa002cf f9b7200e
Pipeline #26705 canceled with stages
in 1 minute and 2 seconds
......@@ -18,7 +18,9 @@ from backend.models.location import CityTransport
from backend.models.location import CityTourism
from backend.models.location import CityOtherStuff
from backend.models.university import University, Campus, UniversityScholarship
from backend.models.university import University
from backend.models.university import Campus
from backend.models.university import UniversityScholarship
from backend.models.university import UniversityInfo
from backend.models.university import UniversitySemestersDates
......@@ -53,6 +55,7 @@ CLASSIC_MODELS = [
Country,
City,
Currency,
University,
Department,
Offer,
Semester,
......@@ -66,7 +69,6 @@ CLASSIC_MODELS = [
]
VERSIONNED_MODELS = [
University,
Campus,
UniversityScholarship,
UniversityInfo,
......@@ -102,3 +104,6 @@ for model in CLASSIC_MODELS:
for model in VERSIONNED_MODELS:
admin.site.register(model, CompareVersionAdmin)
if (model.get_serializer().Meta.model != model):
raise Exception(
"Get_serializer configuration incorrect in", str(model))
# Generated by Django 2.0.3 on 2018-08-28 19:28
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('backend', '0020_auto_20180827_1026'),
]
operations = [
migrations.RenameModel(
old_name='ForTestingVersionning',
new_name='ForTestingVersioning',
),
]
......@@ -11,6 +11,10 @@ class CityTransport(BasicModule):
city = models.OneToOneField(
City, on_delete=models.CASCADE, related_name='city_transport', primary_key=True)
@classmethod
def get_serializer(cls):
return CityTransportSerializer
class CityTransportSerializer(BasicModuleSerializer):
class Meta:
......@@ -36,6 +40,10 @@ class CityTourism(BasicModule):
city = models.OneToOneField(
City, on_delete=models.CASCADE, related_name='city_tourism', primary_key=True)
@classmethod
def get_serializer(cls):
return CityTourismSerializer
class CityTourismSerializer(BasicModuleSerializer):
class Meta:
......@@ -61,6 +69,10 @@ class CityOtherStuff(BasicModule):
city = models.OneToOneField(
City, on_delete=models.CASCADE, related_name='city_other_stuff', primary_key=True)
@classmethod
def get_serializer(cls):
return CityOtherStuffSerializer
class CityOtherStuffSerializer(BasicModuleSerializer):
class Meta:
......
......@@ -10,6 +10,10 @@ class CityPhoto(Photo):
on_delete=models.CASCADE,
related_name='city_photos')
@classmethod
def get_serializer(cls):
return CityPhotoSerializer
class Meta:
unique_together = ('city', 'photo')
......
......@@ -11,6 +11,10 @@ class CountryVisaAdministrative(BasicModule):
country = models.OneToOneField(
Country, on_delete=models.CASCADE, related_name='country_visa_administrative', primary_key=True)
@classmethod
def get_serializer(cls):
return CountryVisaAdministrativeSerializer
class CountryVisaAdministrativeSerializer(BasicModuleSerializer):
class Meta:
......@@ -37,6 +41,10 @@ class CountryHealthInsurance(BasicModule):
country = models.OneToOneField(
Country, on_delete=models.CASCADE, related_name='country_health_insurances', primary_key=True)
@classmethod
def get_serializer(cls):
return CountryHealthInsuranceSerializer
class CountryHealthInsuranceSerializer(BasicModuleSerializer):
class Meta:
......@@ -62,6 +70,10 @@ class CountryOtherInsurance(BasicModule):
country = models.OneToOneField(
Country, on_delete=models.CASCADE, related_name='country_other_insurances', primary_key=True)
@classmethod
def get_serializer(cls):
return CountryOtherInsuranceSerializer
class CountryOtherInsuranceSerializer(BasicModuleSerializer):
class Meta:
......@@ -86,6 +98,10 @@ class CountryCulture(BasicModule):
country = models.OneToOneField(
Country, on_delete=models.CASCADE, related_name='country_culture', primary_key=True)
@classmethod
def get_serializer(cls):
return CountryCultureSerializer
class CountryCultureSerializer(BasicModuleSerializer):
class Meta:
......@@ -110,6 +126,10 @@ class CountryTransport(BasicModule):
country = models.OneToOneField(
Country, on_delete=models.CASCADE, related_name='country_transport', primary_key=True)
@classmethod
def get_serializer(cls):
return CountryTransportSerializer
class CountryTransportSerializer(BasicModuleSerializer):
class Meta:
......@@ -135,6 +155,10 @@ class CountryTourism(BasicModule):
country = models.OneToOneField(
Country, on_delete=models.CASCADE, related_name='country_tourism', primary_key=True)
@classmethod
def get_serializer(cls):
return CountryTourismSerializer
class CountryTourismSerializer(BasicModuleSerializer):
class Meta:
......@@ -160,6 +184,10 @@ class CountryOtherStuff(BasicModule):
country = models.OneToOneField(
Country, on_delete=models.CASCADE, related_name='country_other_stuff', primary_key=True)
@classmethod
def get_serializer(cls):
return CountryOtherStuffSerializer
class CountryOtherStuffSerializer(BasicModuleSerializer):
class Meta:
......
......@@ -7,6 +7,10 @@ class CountryDri(DriRestrictedModule):
country = models.ManyToManyField(
Country, related_name="country_dri")
@classmethod
def get_serializer(cls):
return CountryDriSerializer
class CountryDriSerializer(DriRestrictedModuleSerializer):
......
......@@ -10,6 +10,10 @@ class CountryPhoto(Photo):
on_delete=models.CASCADE,
related_name='country_photos')
@classmethod
def get_serializer(cls):
return CountryPhotoSerializer
class Meta:
unique_together = ('country', 'photo')
......
......@@ -7,6 +7,10 @@ class CountryScholarship(Scholarship):
country = models.ManyToManyField(
Country, related_name="country_scholarhip")
@classmethod
def get_serializer(cls):
return CountryScholarshipSerializer
class CountryScholarshipSerializer(ScholarshipSerializer):
......
......@@ -3,5 +3,5 @@ from .myModelSerializer import MyModelSerializer # noqa: F401
from .myModelViewSet import MyModelViewSet # noqa: F401
from .pendingModeration import PendingModeration, PendingModerationSerializer, PendingModerationViewSet # noqa: F401
from .forTestingModeration import ForTestingModeration, ForTestingModerationSerializer, ForTestingModerationViewSet # noqa: F401
from .myModelVersionned import MyModelVersionned, MyModelVersionnedSerializer, MyModelVersionnedViewSet # noqa: F401
from .forTestingVersionning import ForTestingVersionning, ForTestingVersionningSerializer, ForTestingVersionningViewSet # noqa: F401
from .myModelVersionned import MyModelVersionned, MyModelVersionnedSerializer, MyModelVersionnedViewSet, VersionViewSet # noqa: F401
from .forTestingVersioning import ForTestingVersioning, ForTestingVersioningSerializer, ForTestingVersioningViewSet # noqa: F401
......@@ -4,25 +4,29 @@ import reversion
@reversion.register()
class ForTestingVersionning(MyModelVersionned):
class ForTestingVersioning(MyModelVersionned):
"""
Simple model for testing purposes
"""
bbb = models.CharField(max_length=100)
@classmethod
def get_serializer(cls):
return ForTestingVersioningSerializer
class ForTestingVersionningSerializer(MyModelVersionnedSerializer):
class ForTestingVersioningSerializer(MyModelVersionnedSerializer):
"""
Same as above
"""
class Meta:
model = ForTestingVersionning
model = ForTestingVersioning
fields = '__all__'
class ForTestingVersionningViewSet(MyModelVersionnedViewSet):
class ForTestingVersioningViewSet(MyModelVersionnedViewSet):
"""
Same as above
"""
serializer_class = ForTestingVersionningSerializer
queryset = ForTestingVersionning.objects.all()
serializer_class = ForTestingVersioningSerializer
queryset = ForTestingVersioning.objects.all()
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericRelation
from .pendingModeration import PendingModeration
class MyModel(models.Model):
......@@ -17,6 +19,7 @@ class MyModel(models.Model):
updated_on = models.DateTimeField(null=True)
updated_by = models.ForeignKey(
User, null=True, on_delete=models.SET_NULL, related_name='+')
pending_moderation = GenericRelation(PendingModeration)
class Meta:
abstract = True
......@@ -5,6 +5,7 @@ from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from backend.utils import is_member
from .myModel import MyModel
from .pendingModeration import PendingModerationSerializer
class MyModelSerializer(serializers.ModelSerializer):
......@@ -14,6 +15,8 @@ class MyModelSerializer(serializers.ModelSerializer):
format="%Y-%m-%d %H:%M:%S", read_only=True)
moderated_by = serializers.CharField(read_only=True)
updated_by = serializers.CharField(read_only=True)
pending_moderation = serializers.SerializerMethodField()
# pending_moderation = PendingModerationSerializer(many=True, read_only=True)
# TODO : updated_by_username useless ? See in rest API
updated_by_username = serializers.SerializerMethodField(read_only=True)
......@@ -24,6 +27,12 @@ class MyModelSerializer(serializers.ModelSerializer):
else:
return None
def get_pending_moderation(self, obj):
if self.context['view'].action != 'list':
print(self.context['view'].action)
return PendingModerationSerializer(obj.pending_moderation, many=True, read_only=True, context=self.context).data
return None
class Meta:
model = MyModel
......@@ -84,6 +93,9 @@ class MyModelSerializer(serializers.ModelSerializer):
pass
def save(self, **kwargs):
return self.my_save(**kwargs)
def my_save(self, **kwargs):
user = self.get_user_in_request()
self.clean_validated_data()
self.my_pre_save()
......
from backend.models.my_model import MyModel, MyModelSerializer, MyModelViewSet
from backend.signals import new_revision_saved
from rest_framework import serializers, permissions, mixins, viewsets
import reversion
from reversion.models import Version
from django.contrib.contenttypes.models import ContentType
from django.core.serializers.base import DeserializationError
from django.core import serializers as djangoSerializers
class MyModelVersionned(MyModel):
@classmethod
def get_serializer(cls):
"""
This function is required for handling
versioning easily.
You have to put the correct value in each subclass
"""
raise Exception("Get_serializer must be redifined in subclass")
class Meta:
abstract = True
class MyModelVersionnedSerializer(MyModelSerializer):
nb_versions = serializers.SerializerMethodField()
content_type_id = serializers.SerializerMethodField()
def get_nb_versions(self, obj):
if self.context['view'].action != 'list':
versions = Version.objects.get_for_object(obj)
return len(versions)
return None
def get_content_type_id(self, obj):
return ContentType.objects.get_for_model(self.Meta.model).id
def save(self, **kwargs):
# Retrieve the user info from the request
user = self.context['request'].user
res = None
with reversion.create_revision():
super(MyModelSerializer, self).save(**kwargs)
res = self.my_save(**kwargs)
reversion.set_user(user)
# Signal save to perform squash of revisions
new_revision_saved.send(sender=self.__class__, obj=self.instance)
return res
class MyModelVersionnedViewSet(MyModelViewSet):
serializer_class = MyModelVersionnedSerializer
class VersionSerializer(serializers.ModelSerializer):
# serialized_data = serializers.JSONField()
data = serializers.SerializerMethodField()
def get_data(self, obj):
data = obj.serialized_data
try:
tmp = list(djangoSerializers.deserialize(
obj.format, data, ignorenonexistent=True))[0]
print(tmp.object)
# Version is valid,
obj_serializer = tmp.object.get_serializer()
if obj_serializer is None:
return "THE PROGRAMMER FORGOT TO SET THE SERIALIZER IN THE MODEL"
else:
new_context = dict(self.context)
new_context['view'].action = 'list'
return obj_serializer(tmp.object, context=new_context).data
except (DeserializationError, djangoSerializers.SerializerDoesNotExist):
obj.delete() # The version is not valid regarding the model, we should delete it !
# Might result in inconsistent nb of versions but that's fine.
return None
class Meta:
model = Version
fields = ('data', 'id')
class VersionViewSet(mixins.ListModelMixin,
viewsets.GenericViewSet): # TODO better presentation
permission_classes = (permissions.IsAuthenticated,)
serializer_class = VersionSerializer
def get_queryset(self):
content_type_id = self.kwargs['content_type_id']
object_pk = self.kwargs['object_pk']
ct = ContentType.objects.get_for_id(content_type_id)
model = ct.model_class()
obj = model.objects.get(pk=object_pk)
return Version.objects.get_for_object(obj)
# queryset = get_queryset()
......@@ -17,7 +17,7 @@ class MyModelViewSet(DictModeViewSet):
to prefetch some table and enhance performances
"""
self.mymodel_queryset = self.queryset.prefetch_related(
'moderated_by', 'updated_by')
'moderated_by', 'updated_by', 'pending_moderation')
return self.extend_queryset()
def extend_queryset(self):
......
......@@ -21,6 +21,15 @@ class PendingModeration(models.Model):
class PendingModerationSerializer(serializers.ModelSerializer):
content_type = serializers.CharField(read_only=True)
object_id = serializers.CharField(read_only=True)
updated_by = serializers.CharField(read_only=True)
updated_on = serializers.DateTimeField(
format="%Y-%m-%d %H:%M:%S", read_only=True)
new_object = serializers.JSONField(read_only=True)
class Meta:
model = PendingModeration
fields = '__all__'
......
......@@ -31,6 +31,10 @@ class Campus(BasicModule):
def location(self):
return {'lat': self.lat, 'lon': self.lon}
@classmethod
def get_serializer(cls):
return CampusSerializer
class Meta:
unique_together = ('is_main_campus', 'university')
......
......@@ -11,6 +11,10 @@ class CampusCultureAndStudentLife(BasicModule):
campus = models.OneToOneField(
Campus, on_delete=models.CASCADE, related_name='campus_culture_and_sl', primary_key=True)
@classmethod
def get_serializer(cls):
return CampusCultureAndStudentLifeSerializer
class CampusCultureAndStudentLifeSerializer(BasicModuleSerializer):
class Meta:
......@@ -34,6 +38,10 @@ class CampusAccommodation(BasicModule):
campus = models.OneToOneField(
Campus, on_delete=models.CASCADE, related_name='campus_accomodation', primary_key=True)
@classmethod
def get_serializer(cls):
return CampusAccommodationSerializer
class CampusAccommodationSerializer(BasicModuleSerializer):
class Meta:
......@@ -57,6 +65,10 @@ class CampusTransport(BasicModule):
campus = models.OneToOneField(
Campus, on_delete=models.CASCADE, related_name='campus_transport', primary_key=True)
@classmethod
def get_serializer(cls):
return CampusTransportSerializer
class CampusTransportSerializer(BasicModuleSerializer):
class Meta:
......@@ -81,6 +93,10 @@ class CampusOtherStuff(BasicModule):
campus = models.OneToOneField(
Campus, on_delete=models.CASCADE, related_name='campus_other_stuff', primary_key=True)
@classmethod
def get_serializer(cls):
return CampusOtherStuffSerializer
class CampusOtherStuffSerializer(BasicModuleSerializer):
class Meta:
......
......@@ -12,6 +12,10 @@ class UniversityInsurance(BasicModule):
university = models.OneToOneField(
University, on_delete=models.CASCADE, related_name='university_insurances', primary_key=True)
@classmethod
def get_serializer(cls):
return UniversityInsuranceSerializer
class UniversityInsuranceSerializer(BasicModuleSerializer):
class Meta:
......@@ -35,6 +39,10 @@ class UniversityCulture(BasicModule):
university = models.OneToOneField(
University, on_delete=models.CASCADE, related_name='university_culture', primary_key=True)
@classmethod
def get_serializer(cls):
return UniversityCultureSerializer
class UniversityCultureSerializer(BasicModuleSerializer):
class Meta:
......@@ -58,6 +66,10 @@ class UniversityCourses(BasicModule):
university = models.OneToOneField(
University, on_delete=models.CASCADE, related_name='university_courses', primary_key=True)
@classmethod
def get_serializer(cls):
return UniversityCoursesSerializer
class UniversityCoursesSerializer(BasicModuleSerializer):
class Meta:
......@@ -83,6 +95,10 @@ class UniversitySpecialOffer(BasicModule):
university = models.OneToOneField(
University, on_delete=models.CASCADE, related_name='university_special_offer', primary_key=True)
@classmethod
def get_serializer(cls):
return UniversitySpecialOfferSerializer
class UniversitySpecialOfferSerializer(BasicModuleSerializer):
class Meta:
......@@ -107,6 +123,10 @@ class UniversityOtherStuff(BasicModule):
university = models.OneToOneField(
University, on_delete=models.CASCADE, related_name='university_other_stuff', primary_key=True)
@classmethod
def get_serializer(cls):
return UniversityOtherStuffSerializer
class UniversityOtherStuffSerializer(BasicModuleSerializer):
class Meta:
......
......@@ -7,6 +7,10 @@ class UniversityDri(DriRestrictedModule):
university = models.ManyToManyField(
University, related_name="university_dri")
@classmethod
def get_serializer(cls):
return UniversityDriSerializer
class UniversityDriSerializer(DriRestrictedModuleSerializer):
......
......@@ -26,6 +26,10 @@ class UniversityInfo(BasicModule):
costs_currency = models.ForeignKey(Currency, on_delete=models.PROTECT)
@classmethod
def get_serializer(cls):
return UniversityInfoSerializer
class UniversityInfoSerializer(BasicModuleSerializer):
......
......@@ -10,6 +10,10 @@ class UniversityPhoto(Photo):
on_delete=models.CASCADE,
related_name='university_photos')
@classmethod
def get_serializer(cls):
return UniversityPhotoSerializer
class Meta:
unique_together = ('university', 'photo')
......
......@@ -7,6 +7,10 @@ class UniversityScholarship(Scholarship):
university = models.ForeignKey(
University, on_delete=models.PROTECT, null=False, related_name="univScholarship")
@classmethod
def get_serializer(cls):
return UniversityScholarshipSerializer
class UniversityScholarshipSerializer(ScholarshipSerializer):
......
......@@ -24,6 +24,9 @@ class UniversitySemestersDates(BasicModule):
autumn_begin = models.DateField(null=True, blank=True)
autumn_end = models.DateField(null=True, blank=True)
@classmethod
def get_serializer(cls):
return UniversitySemestersDatesSerializer
# Need custom validation
# TODO add tests
......
......@@ -6,8 +6,6 @@ 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\
......
from backend.models.my_model import ForTestingVersionning
from backend.models.my_model import ForTestingVersioning
from reversion.models import Version
from backend.signals import squashRevisionByUser
import reversion
......@@ -9,7 +9,7 @@ import json
class SquashVersionsTestCase(WithUserTestCase):
def setUp(self):
self.obj = ForTestingVersionning(bbb="v1")
self.obj = ForTestingVersioning(bbb="v1")
with reversion.create_revision():
self.obj.save()
reversion.set_user(self.authenticated_user)
......
from .withUserTestCase import WithUserTestCase
from backend.models.my_model import ForTestingVersionning
from backend.models.my_model import ForTestingVersioning
from django.conf import settings
from reversion.models import Version
from backend.signals import new_revision_saved
from django.test import override_settings
class VersionningTestCase(WithUserTestCase):
class VersioningTestCase(WithUserTestCase):
@classmethod
def setUpMoreTestData(cls):
cls.testing_model = ForTestingVersionning
cls.api_versionning = '/api/test/versionning/'
cls.testing_model = ForTestingVersioning
cls.api_versioning = '/api/test/versioning/'
def reset_signal_called(self):
self.signal_was_called = False
......@@ -22,7 +23,7 @@ class VersionningTestCase(WithUserTestCase):
def _submit_put_test(self, client, data, pk):
response = client.put(
self.api_versionning + str(pk) + '/',
self.api_versioning + str(pk) + '/',
data,
format='json'
)
......@@ -38,10 +39,10 @@ class VersionningTestCase(WithUserTestCase):
return matching[0]
####
def test_versionning(self):
@override_settings(MODERATION_ACTIVATED=False)
def test_versioning(self):
"""
Test to check that versionning is working
Test to check that versioning is working
We also check that new_revision_saved is called
"""
......@@ -52,7 +53,7 @@ class VersionningTestCase(WithUserTestCase):
data_1 = {'bbb': "Test 1"}
response = self.authenticated_client.post(
self.api_versionning,
self.api_versioning,
data_1,
format='json'
)
......@@ -66,7 +67,7 @@ class VersionningTestCase(WithUserTestCase):
data_2 = {'bbb': "Test 2"}
response = self.authenticated_client_2.put(
self.api_versionning + str(instance.pk) + '/',
self.api_versioning + str(instance.pk) + '/',
data_2,
format='json'
)
......
......@@ -53,6 +53,7 @@ from backend.models.user import PreviousDepartureFeedbackViewSet
from backend.models.user import UserDataViewSet
from backend.models.my_model import PendingModerationViewSet
from backend.models.my_model import VersionViewSet
from rest_framework.documentation import include_docs_urls
......@@ -67,9 +68,9 @@ router = routers.DefaultRouter()
if settings.TESTING:
from backend.models.my_model import ForTestingModerationViewSet
from backend.models.my_model import ForTestingVersionningViewSet
from backend.models.my_model import ForTestingVersioningViewSet
router.register(r'test/moderation', ForTestingModerationViewSet)
router.register(r'test/versionning', ForTestingVersionningViewSet)
router.register(r'test/versioning', ForTestingVersioningViewSet)
router.register(r'country', CountryViewSet)
router.register(r'city', CityViewSet)
......@@ -134,5 +135,7 @@ router.register(r'user/recommendations_list', RecommendationListViewSet)
router.register(r'user/data', UserDataViewSet, "user_data-detail")
router.register(r'pending_moderation', PendingModerationViewSet)
router.register(
r'version/(?P<content_type_id>[0-9]+)/(?P<object_pk>[0-9A-Za-z]+)', VersionViewSet, "versions-list")
urlpatterns += [url(r'^api/', include(router.urls))]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!