Commit a97d8933 authored by Florent Chehab's avatar Florent Chehab

Merge branch 'centralized_viewset_permissions' into 'master'

Centralized viewset permissions, added obj_moderation_level [not tested], cleaning

See merge request chehabfl/outgoing_rex!25
parents e9e8ecbe 108fa917
Pipeline #26855 passed with stages
in 2 minutes and 47 seconds
......@@ -5,7 +5,7 @@ install_backend:
pip install -r requirements.txt --quiet
generate_backend:
python ./backend/generate/generate_all.py
export PYTHONPATH=$$PWD ; python ./backend/generate/generate_all.py
generate_frontend:
python ./frontend/generate/generate_frontend_files.py
......
#####
# This python file is used to generate js files for redux
from django import template
import yaml
from os.path import join, realpath, dirname
from general.api import get_api_config
############
# Need to do this first so that Django template engine is working
......@@ -44,7 +44,7 @@ saving_dir = realpath(current_dir + "/../")
# API_BASE = "http://127.0.0.1:8000/api/"
api_config = yaml.load(read_file(join(current_dir, 'api_config.yml')))
api_config = get_api_config()
# Render urls.py
template_path = join(templates_dir, 'urls.tpl')
......
......@@ -9,11 +9,18 @@ from django.conf.urls import url, include
from django.conf import settings
from rest_framework import routers
{% spaceless %}
{% for model in data %}{% if 'requires_testing' not in model or not model.requires_testing %}
ALL_MODELS = []
ALL_VIEWSETS = []
{% for model in data %}
{% if not model.requires_testing %}
from backend.models.{{model.import_location}} import {{model.viewset}}
{% endif %}{% endfor %}
{% endspaceless %}
ALL_VIEWSETS.append({{model.viewset}})
{% if model.model is not None and not model.ignore_in_admin%}
from backend.models.{{model.import_location}} import {{model.model}}
ALL_MODELS.append({{model.model}})
{% endif %}
{% endif %}
{% endfor %}
from rest_framework.documentation import include_docs_urls
......@@ -24,19 +31,37 @@ urlpatterns = [
router = routers.DefaultRouter()
if settings.TESTING:
{% for model in data %}{% if 'requires_testing' in model or model.requires_testing %}
{% for model in data %}{% if model.requires_testing %}
from backend.models.{{model.import_location}} import {{model.viewset}}
ALL_VIEWSETS.append({{model.viewset}})
{% if model.model is not None %}
from backend.models.{{model.import_location}} import {{model.model}}
ALL_MODELS.append({{model.model}})
{% endif %}
router.register(
r'{{model.api_end_point}}{% if 'api_attr' in model %}/{{model.api_attr}}{% endif %}',
{{model.viewset}}{%if 'api_name' in model%},"{{model.api_name}}"{% endif %}
){% endif %}{% endfor %}
{% for model in data %}{% if 'requires_testing' not in model or not model.requires_testing %}
{% for model in data %}{% if not model.requires_testing %}
router.register(
r'{{model.api_end_point}}{% if 'api_attr' in model %}/{{model.api_attr}}{% endif %}',
{{model.viewset}}{%if 'api_name' in model%},"{{model.api_name}}"{% endif %}
){% endif %}{% endfor %}
urlpatterns += [url(r'^api/', include(router.urls))]
for model in ALL_MODELS:
for key in model.model_config:
val = model.model_config[key]
if val is None:
raise Exception("You forgot to set the {} config variable in the model {}".format(key, str(model)))
from backend.permissions import DEFAULT_VIEWSET_PERMISSIONS
for viewset in ALL_VIEWSETS:
for p in DEFAULT_VIEWSET_PERMISSIONS:
v_p = viewset.permission_classes
if p not in v_p:
raise Exception("Permission_classes are not defined correctly in the viewset {}".format(str(viewset)))
{% endautoescape %}
This diff is collapsed.
# Generated by Django 2.0.3 on 2018-09-02 15:16
import backend.models.my_model.myModel
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('backend', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='campus',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='campustaggeditem',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='city',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='citytaggeditem',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='country',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='countrydri',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='countryscholarship',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='countrytaggeditem',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='currency',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='department',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='fortestingmoderation',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='fortestingversioning',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='offer',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='previousdeparture',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='previousdeparturefeedback',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='recommendation',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='recommendationlist',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='semester',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='specialty',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='tag',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='university',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='universitydri',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='universityinfo',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='universityscholarship',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='universitysemestersdates',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='universitytaggeditem',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
migrations.AlterField(
model_name='userdata',
name='obj_moderation_level',
field=models.SmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), backend.models.my_model.myModel.validate_obj_model_lv]),
),
]
......@@ -2,9 +2,12 @@ from django.db import models
from rest_framework import serializers
from backend.models.location import Country
from backend.models.my_model import MyModel, MyModelSerializer, MyModelVersionnedViewSet
from backend.utils import get_model_config, get_viewset_permissions
class City(MyModel):
model_config = get_model_config("City")
name = models.CharField(max_length=200)
local_name = models.CharField(max_length=200, null=True, blank=True)
# We add an area to distinguish similarly named cities
......@@ -29,5 +32,6 @@ class CitySerializer(MyModelSerializer):
class CityViewSet(MyModelVersionnedViewSet):
permission_classes = get_viewset_permissions("CityViewSet")
queryset = City.objects.all() # pylint: disable=E1101
serializer_class = CitySerializer
from django.db import models
from backend.models.tag import TaggedItem, TaggedItemSerializer, TaggedItemViewSet
from backend.models.location import City
from backend.utils import get_model_config, get_viewset_permissions
class CityTaggedItem(TaggedItem):
model_config = get_model_config("CityTaggedItem")
city = models.OneToOneField(
City, on_delete=models.PROTECT, related_name='city_items', primary_key=True)
......@@ -22,6 +25,6 @@ class CityTaggedItemSerializer(TaggedItemSerializer):
class CityTaggedItemViewSet(TaggedItemViewSet):
permission_classes = get_viewset_permissions("CityTaggedItemViewSet")
queryset = CityTaggedItem.objects.all() # pylint: disable=E1101
serializer_class = CityTaggedItemSerializer
from django.db import models
from backend.models.my_model import MyModel, MyModelSerializer, MyModelVersionnedViewSet
from backend.utils import get_model_config, get_viewset_permissions
# Data model based on : https://unstats.un.org/unsd/methodology/m49/overview/
class Country(MyModel):
model_config = get_model_config("Country")
name = models.CharField(max_length=200)
iso_alpha2_code = models.CharField(primary_key=True, max_length=2)
iso_alpha3_code = models.CharField(
......@@ -27,5 +29,6 @@ class CountrySerializer(MyModelSerializer):
class CountryViewSet(MyModelVersionnedViewSet):
permission_classes = get_viewset_permissions("CountryViewSet")
queryset = Country.objects.all() # pylint: disable=E1101
serializer_class = CountrySerializer
from django.db import models
from backend.models.location import Country
from backend.models.module import DriRestrictedModule, DriRestrictedModuleSerializer, DriRestrictedModuleViewSet
from backend.utils import get_model_config, get_viewset_permissions
class CountryDri(DriRestrictedModule):
model_config = get_model_config("CountryDri")
country = models.ManyToManyField(
Country, related_name="country_dri")
......@@ -20,5 +22,6 @@ class CountryDriSerializer(DriRestrictedModuleSerializer):
class CountryDriViewSet(DriRestrictedModuleViewSet):
permission_classes = get_viewset_permissions("CountryDriViewSet")
queryset = CountryDri.objects.all() # pylint: disable=E1101
serializer_class = CountryDriSerializer
from django.db import models
from backend.models.location import Country
from backend.models.module import Scholarship, ScholarshipSerializer, ScholarshipViewSet
from backend.utils import get_model_config, get_viewset_permissions
class CountryScholarship(Scholarship):
model_config = get_model_config("CountryScholarship")
country = models.ManyToManyField(
Country, related_name="country_scholarhip")
......@@ -20,5 +22,6 @@ class CountryScholarshipSerializer(ScholarshipSerializer):
class CountryScholarshipViewSet(ScholarshipViewSet):
permission_classes = get_viewset_permissions("CountryScholarshipViewSet")
queryset = CountryScholarship.objects.all() # pylint: disable=E1101
serializer_class = CountryScholarshipSerializer
from django.db import models
from backend.models.tag import TaggedItem, TaggedItemSerializer, TaggedItemViewSet
from backend.models.location import Country
from backend.utils import get_model_config, get_viewset_permissions
class CountryTaggedItem(TaggedItem):
model_config = get_model_config("CountryTaggedItem")
country = models.OneToOneField(
Country, on_delete=models.PROTECT, related_name='country_items', primary_key=True)
......@@ -22,6 +24,6 @@ class CountryTaggedItemSerializer(TaggedItemSerializer):
class CountryTaggedItemViewSet(TaggedItemViewSet):
permission_classes = get_viewset_permissions("CountryTaggedItemViewSet")
queryset = CountryTaggedItem.objects.all() # pylint: disable=E1101
serializer_class = CountryTaggedItemSerializer
from django.db import models
from rest_framework import permissions
from django.core.validators import MinValueValidator
from backend.models.my_model import MyModel, MyModelSerializer, MyModelViewSet
from backend.utils import get_model_config, get_viewset_permissions
class Currency(MyModel):
model_config = get_model_config("Currency")
code = models.CharField(primary_key=True, max_length=3)
name = models.CharField(max_length=100)
symbol = models.CharField(null=True, blank=True, max_length=30)
......@@ -22,6 +24,6 @@ class CurrencySerializer(MyModelSerializer):
class CurrencyViewSet(MyModelViewSet):
permission_classes = (permissions.IsAdminUser,) # TODO : change
permission_classes = get_viewset_permissions("CurrencyViewSet")
queryset = Currency.objects.all() # pylint: disable=E1101
serializer_class = CurrencySerializer
......@@ -16,7 +16,7 @@ class BasicModule(MyModelVersionned):
class BasicModuleSerializer(MyModelVersionnedSerializer):
def validate(self, attrs):
def my_validate(self, attrs):
content = {'usefull_links': attrs['usefull_links']}
config = {'usefull_links': USEFULL_LINKS_CONFIG}
validate_content_against_config(config, content)
......
......@@ -27,7 +27,7 @@ class Scholarship(BasicModule):
class ScholarshipSerializer(BasicModuleSerializer):
def validate(self, attrs):
def my_validate(self, attrs):
if attrs['amount_max'] < attrs['amount_min']:
raise serializers.ValidationError(
"Amount_max should be greater or equal than amount_min")
......
......@@ -2,12 +2,14 @@ from .myModel import MyModel
from .myModelSerializer import MyModelSerializer
from .myModelViewSet import MyModelViewSet
from django.db import models
from backend.utils import get_model_config, get_viewset_permissions
class ForTestingModeration(MyModel):
"""
Simple model for testing purposes
"""
model_config = get_model_config("ForTestingModeration")
aaa = models.CharField(max_length=100)
......@@ -24,5 +26,6 @@ class ForTestingModerationViewSet(MyModelViewSet):
"""
Same as above
"""
permission_classes = get_viewset_permissions("ForTestingModerationViewSet")
serializer_class = ForTestingModerationSerializer
queryset = ForTestingModeration.objects.all()
from .myModelVersionned import MyModelVersionned, MyModelVersionnedSerializer, MyModelVersionnedViewSet
from django.db import models
import reversion
from backend.utils import get_model_config, get_viewset_permissions
@reversion.register()
......@@ -8,6 +9,7 @@ class ForTestingVersioning(MyModelVersionned):
"""
Simple model for testing purposes
"""
model_config = get_model_config("ForTestingVersioning")
bbb = models.CharField(max_length=100)
@classmethod
......@@ -28,5 +30,6 @@ class ForTestingVersioningViewSet(MyModelVersionnedViewSet):
"""
Same as above
"""
permission_classes = get_viewset_permissions("ForTestingVersioningViewSet")
serializer_class = ForTestingVersioningSerializer
queryset = ForTestingVersioning.objects.all()
......@@ -2,6 +2,19 @@ from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericRelation
from .pendingModeration import PendingModeration
from backend.permissions import OBJ_MODERATION_PERMISSIONS
from backend.permissions import DEFAULT_OBJ_MODERATION_LV
from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError
oml = DEFAULT_OBJ_MODERATION_LV
POSSIBLE_OBJ_MODER_LV = [OBJ_MODERATION_PERMISSIONS[key]
for key in OBJ_MODERATION_PERMISSIONS]
def validate_obj_model_lv(value):
if value not in POSSIBLE_OBJ_MODER_LV:
raise ValidationError('obj_moderation_level not recognized')
class MyModel(models.Model):
......@@ -19,7 +32,13 @@ class MyModel(models.Model):
updated_on = models.DateTimeField(null=True)
updated_by = models.ForeignKey(
User, null=True, on_delete=models.SET_NULL, related_name='+')
obj_moderation_level = models.SmallIntegerField(
default=oml, validators=[MinValueValidator(0), validate_obj_model_lv])
pending_moderation = GenericRelation(PendingModeration)
class Meta:
abstract = True
model_config = {
"moderation_level": None,
}
from rest_framework import serializers
from rest_framework.validators import ValidationError
from django.utils import timezone
from .pendingModeration import PendingModeration
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
from backend.utils import get_user_level
from backend.permissions import is_moderation_required
CLEANED_MY_MODEL_DATA = {
'moderated_by': None,
......@@ -30,16 +30,10 @@ class MyModelSerializer(serializers.ModelSerializer):
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)
model_config = serializers.SerializerMethodField()
def get_updated_by_username(self, obj):
if obj.updated_by:
return obj.updated_by.username
else:
return None
def get_model_config(self, obj=None):
return self.Meta.model.model_config
def get_pending_moderation(self, obj):
if self.context['view'].action != 'list':
......@@ -49,47 +43,41 @@ class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
def moderation_required(self):
if hasattr(self.Meta, 'NEVER_MODERATE') and self.Meta.NEVER_MODERATE:
return False
user = self.get_user_in_request()
request = self.context['request']
def my_validate(self, attrs):
return attrs
if user.is_staff:
return False
def validate(self, attrs):
"""
TODO unit test this
"""
self.user = self.context['request'].user
self.user_level = get_user_level(self.user)
if settings.MODERATION_ACTIVATED:
if request.method in ['POST', 'PUT']: # should be always the case
return not is_member('Moderators', request.user)
if "obj_moderation_level" in attrs:
requested_obj_moder_lv = attrs["obj_moderation_level"]
return False
if requested_obj_moder_lv > self.user_level:
raise ValidationError(
"You can't request moderation for a higher rank than you.")
def get_user_in_request(self):
return self.context['request'].user
return self.my_validate(attrs)
def set_model_attr_no_moder(self, moderated_and_updated):
user = self.get_user_in_request()
now = timezone.now()
self.override_validated_data({
'moderated_by': user,
'moderated_by': self.user,
'moderated_on': now,
})
if moderated_and_updated:
self.override_validated_data({
'updated_by': user,
'updated_by': self.user,
'updated_on': now,
})
def clean_validated_data(self):
self.override_validated_data(CLEANED_MY_MODEL_DATA)
def get_pending_models(self):
ct = ContentType.objects.get_for_model(self.Meta.model)
return PendingModeration.objects.filter(
content_type=ct, object_id=self.instance.pk)
def override_validated_data(self, new_data):
"""
Method used to force specific attributes when saving a model
......@@ -104,41 +92,34 @@ class MyModelSerializer(serializers.ModelSerializer):
return self.my_save(*args, **kwargs)
def my_save(self, *args, **kwargs):
user = self.get_user_in_request()
self.clean_validated_data()
self.my_pre_save()
ct = ContentType.objects.get_for_model(self.Meta.model)
if self.moderation_required():
if is_moderation_required(self.get_model_config()['moderation_level'], self.instance, self.user, self.user_level):
if self.instance is None: # we need to create the main model
self.instance = super(
MyModelSerializer, self).save(*args, **kwargs)
objs_pending_db = self.get_pending_models()
data_to_save = dict()
for key in self.validated_data:
try:
# retreive the submitted data