Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Rex Dri
Rex Dri
Commits
7266140c
Commit
7266140c
authored
Feb 24, 2019
by
Florent Chehab
Browse files
Merge branch 'cleaning' into 'master'
Cleaning See merge request
rex-dri/rex-dri!50
parents
0e42118e
4fe1bdf2
Pipeline
#35396
passed with stages
in 4 minutes and 15 seconds
Changes
106
Pipelines
1
Show whitespace changes
Inline
Side-by-side
backend/backend_app/models/abstract/my_model/myModelSerializer.py
View file @
7266140c
from
django.contrib.contenttypes.models
import
ContentType
from
django.utils
import
timezone
from
backend_app.custom
import
MySerializerWithJSON
from
backend_app.permissions
import
is_moderation_required
from
backend_app.utils
import
get_user_level
from
rest_framework
import
serializers
from
rest_framework.validators
import
ValidationError
from
django.utils
import
timezone
from
.pendingModeration
import
PendingModeration
from
django.contrib.contenttypes.models
import
ContentType
from
.myModel
import
MyModel
from
.pendingModeration
import
PendingModerationSerializer
from
backend_app.utils
import
get_user_level
from
backend_app.permissions
import
is_moderation_required
from
backend_app.custom
import
MySerializerWithJSON
from
.pendingModeration
import
PendingModeration
,
PendingModerationSerializer
CLEANED_MY_MODEL_DATA
=
{
"moderated_by"
:
None
,
...
...
@@ -17,7 +18,10 @@ CLEANED_MY_MODEL_DATA = {
}
def
override_data
(
old_data
,
new_data
):
def
override_data
(
old_data
:
dict
,
new_data
:
dict
)
->
dict
:
"""Update the data in old_data with the one in new_data
"""
for
key
in
new_data
:
if
key
in
old_data
:
old_data
[
key
]
=
new_data
[
key
]
...
...
@@ -25,10 +29,20 @@ def override_data(old_data, new_data):
class
MyModelSerializer
(
MySerializerWithJSON
):
moderated_on
=
serializers
.
DateTimeField
(
format
=
"%Y-%m-%d %H:%M:%S"
,
read_only
=
True
)
"""Serializer to go along the MyModel Model. This serializer handles backend data moderation checks and tricks.
Raises:
ValidationError -- If you are trying to moderate something you don't have rights to
"""
######
# Basic fields serializers
updated_by
=
serializers
.
CharField
(
read_only
=
True
)
updated_on
=
serializers
.
DateTimeField
(
format
=
"%Y-%m-%d %H:%M:%S"
,
read_only
=
True
)
moderated_by
=
serializers
.
CharField
(
read_only
=
True
)
updated_by
=
serializers
.
CharField
(
read_only
=
True
)
moderated_on
=
serializers
.
DateTimeField
(
format
=
"%Y-%m-%d %H:%M:%S"
,
read_only
=
True
)
pending_moderation
=
serializers
.
SerializerMethodField
()
model_config
=
serializers
.
SerializerMethodField
()
...
...
@@ -36,12 +50,24 @@ class MyModelSerializer(MySerializerWithJSON):
# this is useful when a model has a dedicated primary key
id
=
serializers
.
SerializerMethodField
()
def
get_model_config
(
self
,
obj
=
None
):
def
get_model_config
(
self
,
obj
=
None
)
->
dict
:
"""
Serializer for the `model_config` field.
`obj` is required in the function definition, but it's not used.
"""
return
self
.
Meta
.
model
.
model_config
# Optimization
# Class attribute to force the pending moderation to return the pending moderation data
# Or versionned element to return the number of versions
FORCE_FULL_DISPLAY
=
False
def
get_pending_moderation
(
self
,
obj
):
def
get_pending_moderation
(
self
,
obj
:
MyModel
):
"""
Serializer for the `pending_moderation` field
"""
# Optimization, in list mode, fetching all the pending moderation information is not optimal at all
if
not
self
.
FORCE_FULL_DISPLAY
and
self
.
context
[
"view"
].
action
==
"list"
:
return
None
else
:
...
...
@@ -53,19 +79,28 @@ class MyModelSerializer(MySerializerWithJSON):
pending
,
many
=
True
,
read_only
=
True
,
context
=
self
.
context
).
data
def
get_id
(
self
,
obj
):
def
get_id
(
self
,
obj
:
MyModel
):
"""
Serializer for the id field.
"""
return
obj
.
pk
class
Meta
:
model
=
MyModel
def
my_validate
(
self
,
attrs
):
"""
Function to be redefined in the subclasses to validate class fields.
"""
return
attrs
def
validate
(
self
,
attrs
):
"""
Validate `MyModel` fields and enforce certain field at the backend level.
TODO unit test this
"""
# Enforce fields values based on request
self
.
user
=
self
.
context
[
"request"
].
user
self
.
user_level
=
get_user_level
(
self
.
user
)
...
...
@@ -80,6 +115,10 @@ class MyModelSerializer(MySerializerWithJSON):
return
self
.
my_validate
(
attrs
)
def
set_model_attr_no_moder
(
self
,
moderated_and_updated
):
"""
TODO
TODO: rename ?
"""
now
=
timezone
.
now
()
self
.
override_validated_data
({
"moderated_by"
:
self
.
user
,
"moderated_on"
:
now
})
...
...
@@ -87,9 +126,12 @@ class MyModelSerializer(MySerializerWithJSON):
self
.
override_validated_data
({
"updated_by"
:
self
.
user
,
"updated_on"
:
now
})
def
clean_validated_data
(
self
):
"""
Clear fields related to update and moderation
"""
self
.
override_validated_data
(
CLEANED_MY_MODEL_DATA
)
def
override_validated_data
(
self
,
new_data
):
def
override_validated_data
(
self
,
new_data
:
dict
):
"""
Method used to force specific attributes when saving a model
"""
...
...
@@ -97,12 +139,23 @@ class MyModelSerializer(MySerializerWithJSON):
self
.
validated_data
[
key
]
=
new_data
[
key
]
def
my_pre_save
(
self
):
"""
TODO, Analyse if usefull
"""
pass
def
save
(
self
,
*
args
,
**
kwargs
):
"""
TODO analyse, usefull ?
"""
return
self
.
my_save
(
*
args
,
**
kwargs
)
def
my_save
(
self
,
*
args
,
**
kwargs
):
"""
Function that handles all the moderation in a smart way.
Nothing has to be done to tell that we won't the data to be moderated, it is detected automatically.
"""
self
.
clean_validated_data
()
self
.
my_pre_save
()
ct
=
ContentType
.
objects
.
get_for_model
(
self
.
Meta
.
model
)
...
...
@@ -113,7 +166,7 @@ class MyModelSerializer(MySerializerWithJSON):
if
self
.
instance
is
None
:
# we need to create the main model
# Store the user for squashing data in versions models
self
.
validated_data
.
updated_by
=
self
.
user
self
.
instance
=
super
(
MyModelSerializer
,
self
).
save
(
*
args
,
**
kwargs
)
self
.
instance
=
super
().
save
(
*
args
,
**
kwargs
)
data_to_save
=
dict
()
for
key
in
self
.
validated_data
:
...
...
@@ -126,6 +179,7 @@ class MyModelSerializer(MySerializerWithJSON):
data_to_save
=
override_data
(
data_to_save
,
CLEANED_MY_MODEL_DATA
)
# Save instance into pending moderation state
PendingModeration
.
objects
.
update_or_create
(
content_type
=
ct
,
object_id
=
self
.
instance
.
pk
,
...
...
@@ -137,18 +191,18 @@ class MyModelSerializer(MySerializerWithJSON):
)
return
self
.
instance
else
:
else
:
# Moderation is not needed, we need to check whether it's a moderation or an update with no moderation
moderated_and_updated
=
True
if
self
.
instance
is
None
:
self
.
set_model_attr_no_moder
(
moderated_and_updated
)
return
super
(
MyModelSerializer
,
self
).
save
(
*
args
,
**
kwargs
)
return
super
().
save
(
*
args
,
**
kwargs
)
else
:
try
:
pending_instance
=
PendingModeration
.
objects
.
get
(
content_type
=
ct
,
object_id
=
self
.
instance
.
pk
)
self
.
clean_validated_data
()
# Make that it is done...
self
.
clean_validated_data
()
# Make
quez
that it is done...
# We have to compare the serialized data
# So we make sure to compare the same elements
key_to_remove
=
[]
...
...
@@ -157,6 +211,7 @@ class MyModelSerializer(MySerializerWithJSON):
key_to_remove
.
append
(
key
)
for
key
in
key_to_remove
:
self
.
initial_data
.
pop
(
key
,
None
)
if
pending_instance
.
new_object
==
self
.
initial_data
:
moderated_and_updated
=
False
self
.
validated_data
[
"updated_by"
]
=
pending_instance
.
updated_by
...
...
@@ -167,4 +222,4 @@ class MyModelSerializer(MySerializerWithJSON):
pass
self
.
set_model_attr_no_moder
(
moderated_and_updated
)
return
super
(
MyModelSerializer
,
self
).
save
(
*
args
,
**
kwargs
)
return
super
().
save
(
*
args
,
**
kwargs
)
backend/backend_app/models/abstract/my_model/myModelVersionned.py
View file @
7266140c
from
django.contrib.contenttypes.models
import
ContentType
from
django.core
import
serializers
as
djangoSerializers
from
django.core.serializers.base
import
DeserializationError
import
reversion
from
backend_app.custom
import
MySerializerWithJSON
from
backend_app.models.abstract.my_model
import
(
MyModel
,
MyModelSerializer
,
MyModelViewSet
,
)
from
backend_app.signals.__squash_revision_by_user
import
new_revision_saved
from
rest_framework
import
serializers
,
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
from
backend_app.utils
import
get_viewset_permissions
from
backend_app.custom
import
MySerializerWithJSON
from
rest_framework
import
mixins
,
serializers
,
viewsets
from
reversion.models
import
Version
class
MyModelVersionned
(
MyModel
):
"""
Custom MyModel that will be versionned in the app
"""
@
classmethod
def
get_serializer
(
cls
):
"""
...
...
@@ -29,19 +34,36 @@ class MyModelVersionned(MyModel):
class
MyModelVersionnedSerializer
(
MyModelSerializer
):
"""
Serializer for versionned models
"""
# Add a nb_versions field
nb_versions
=
serializers
.
SerializerMethodField
()
# Add a content_type_id field to be able to find versions
content_type_id
=
serializers
.
SerializerMethodField
()
def
get_nb_versions
(
self
,
obj
):
"""
Serializer for the nb_version field
With a bit of optimization
"""
if
self
.
FORCE_FULL_DISPLAY
or
self
.
context
[
"view"
].
action
!=
"list"
:
versions
=
Version
.
objects
.
get_for_object
(
obj
)
return
len
(
versions
)
return
None
def
get_content_type_id
(
self
,
obj
):
"""
Serializer for content type
"""
return
ContentType
.
objects
.
get_for_model
(
self
.
Meta
.
model
).
id
def
save
(
self
,
*
args
,
**
kwargs
):
"""
Custom save function to use reversion.
"""
with
reversion
.
create_revision
():
res
=
self
.
my_save
(
*
args
,
**
kwargs
)
reversion
.
set_user
(
res
.
updated_by
)
...
...
@@ -50,19 +72,29 @@ class MyModelVersionnedSerializer(MyModelSerializer):
class
MyModelVersionnedViewSet
(
MyModelViewSet
):
"""
Viewset for the versionned models
"""
serializer_class
=
MyModelVersionnedSerializer
class
VersionSerializer
(
MySerializerWithJSON
):
"""
Custom serializer for the (reversion) version model
"""
data
=
serializers
.
SerializerMethodField
()
def
get_data
(
self
,
obj
):
"""
Serilizer for the data field
TODO test
"""
data
=
obj
.
serialized_data
try
:
# We try to deserialize the version
tmp
=
list
(
djangoSerializers
.
deserialize
(
obj
.
format
,
data
,
ignorenonexistent
=
True
)
)[
0
]
...
...
@@ -81,9 +113,12 @@ class VersionSerializer(MySerializerWithJSON):
fields
=
(
"data"
,
"id"
)
class
VersionViewSet
(
mixins
.
ListModelMixin
,
viewsets
.
GenericViewSet
):
# TODO better presentation
class
VersionViewSet
(
mixins
.
ListModelMixin
,
viewsets
.
GenericViewSet
):
"""
Viewset for the versions
TODO better presentation
"""
permission_classes
=
get_viewset_permissions
(
"VersionViewSet"
)
serializer_class
=
VersionSerializer
...
...
backend/backend_app/models/abstract/my_model/myModelViewSet.py
View file @
7266140c
from
.myModelSerializer
import
MyModelSerializer
from
backend_app.permissions
import
DEFAULT_VIEWSET_PERMISSIONS
from
rest_framework
import
viewsets
from
.myModelSerializer
import
MyModelSerializer
class
MyModelViewSet
(
viewsets
.
ModelViewSet
):
"""
Custom default viewset
"""
serializer_class
=
MyModelSerializer
permission_classes
=
DEFAULT_VIEWSET_PERMISSIONS
# Class attribute to tell that return 1 element and not a list of element
# For example when querying userData, we don't specify the userId and query the viewset
# in list mode; still we want to return only one
LIST_SHOULD_BE_DETAIL
=
False
def
list
(
self
,
request
,
*
args
,
**
kwargs
):
response
=
super
(
viewsets
.
ModelViewSet
,
self
).
list
(
# pylint: disable=E1003
request
,
*
args
,
**
kwargs
)
# call the original 'list'
"""
Extend the list function of the viewset class
"""
response
=
super
().
list
(
request
,
*
args
,
**
kwargs
)
# call the original 'list'
if
self
.
LIST_SHOULD_BE_DETAIL
:
if
len
(
response
.
data
)
==
0
:
...
...
backend/backend_app/models/abstract/my_model/pendingModeration.py
View file @
7266140c
from
django.db
import
models
from
rest_framework
import
serializers
,
viewsets
from
django.contrib.contenttypes.models
import
ContentType
from
django.contrib.contenttypes.fields
import
GenericForeignKey
from
backend_app.fields
import
JSONField
from
django.contrib.auth.models
import
User
from
backend_app.utils
import
get_viewset_permissions
,
get_model_config
from
django.contrib.contenttypes.fields
import
GenericForeignKey
from
django.contrib.contenttypes.models
import
ContentType
from
django.db
import
models
from
backend_app.custom
import
MySerializerWithJSON
from
backend_app.fields
import
JSONField
from
backend_app.utils
import
get_model_config
,
get_viewset_permissions
from
rest_framework
import
serializers
,
viewsets
class
PendingModeration
(
models
.
Model
):
"""
Model to hold models that are pending moderation.
"""
model_config
=
get_model_config
(
"PendingModeration"
)
content_type
=
models
.
ForeignKey
(
ContentType
,
on_delete
=
models
.
CASCADE
)
...
...
@@ -18,6 +23,7 @@ class PendingModeration(models.Model):
updated_by
=
models
.
ForeignKey
(
User
,
on_delete
=
models
.
CASCADE
)
updated_on
=
models
.
DateTimeField
(
null
=
True
)
# Object pending moderation
new_object
=
JSONField
(
default
=
dict
)
class
Meta
:
...
...
@@ -25,6 +31,10 @@ class PendingModeration(models.Model):
class
PendingModerationSerializer
(
MySerializerWithJSON
):
"""
Serializer for the Pending Moderation Model
"""
content_type
=
serializers
.
CharField
(
read_only
=
True
)
object_id
=
serializers
.
CharField
(
read_only
=
True
)
...
...
@@ -39,6 +49,10 @@ class PendingModerationSerializer(MySerializerWithJSON):
class
PendingModerationViewSet
(
viewsets
.
ModelViewSet
):
"""
Viewset for the pending moderation model.
"""
permission_classes
=
get_viewset_permissions
(
"PendingModerationViewSet"
)
queryset
=
PendingModeration
.
objects
.
all
()
# pylint: disable=E1101
serializer_class
=
PendingModerationSerializer
backend/backend_app/models/abstract/scholarship/scholarship.py
View file @
7266140c
from
django.core.validators
import
MinValueValidator
from
django.db
import
models
from
backend_app.models.abstract.basic_module
import
(
BasicModule
,
BasicModuleSerializer
,
...
...
@@ -6,7 +8,6 @@ from backend_app.models.abstract.basic_module import (
)
from
backend_app.models.currency
import
Currency
from
rest_framework
import
serializers
from
django.core.validators
import
MinValueValidator
SCHOLARSHIP_FREQUENCIES
=
(
(
"w"
,
"week"
),
...
...
@@ -18,7 +19,11 @@ SCHOLARSHIP_FREQUENCIES = (
class
Scholarship
(
BasicModule
):
"""
Abstract model for scholarships
"""
# TODO change this, don't use python primitive
type
=
models
.
CharField
(
max_length
=
200
)
currency
=
models
.
ForeignKey
(
Currency
,
null
=
True
,
on_delete
=
models
.
PROTECT
)
other_advantages
=
models
.
CharField
(
default
=
""
,
blank
=
True
,
max_length
=
5000
)
...
...
@@ -44,9 +49,16 @@ class Scholarship(BasicModule):
class
ScholarshipSerializer
(
BasicModuleSerializer
):
"""
Serializer for the scholarship class
"""
FORCE_FULL_DISPLAY
=
True
def
my_validate
(
self
,
attrs
):
"""
Custom attribute validation
"""
attrs
=
super
(
ScholarshipSerializer
,
self
).
my_validate
(
attrs
)
if
attrs
[
"amount_min"
]
is
not
None
:
if
attrs
[
"currency"
]
is
None
:
...
...
backend/backend_app/models/abstract/taggedItem/taggedItem.py
View file @
7266140c
from
django.db
import
models
from
backend_app.fields
import
JSONField
from
backend_app.models.abstract.basic_module
import
(
BasicModule
,
BasicModuleSerializer
,
...
...
@@ -6,10 +8,13 @@ from backend_app.models.abstract.basic_module import (
)
from
backend_app.models.tag
import
Tag
from
backend_app.validators.tag
import
tagged_item_validation
from
backend_app.fields
import
JSONField
class
TaggedItem
(
BasicModule
):
"""
Abstract model to represent a tagged item
"""
tag
=
models
.
ForeignKey
(
Tag
,
related_name
=
"+"
,
on_delete
=
models
.
PROTECT
)
custom_content
=
JSONField
(
default
=
dict
)
...
...
@@ -18,6 +23,10 @@ class TaggedItem(BasicModule):
class
TaggedItemSerializer
(
BasicModuleSerializer
):
"""
Serializer for tagged items
"""
FORCE_FULL_DISPLAY
=
True
def
my_validate
(
self
,
attrs
):
...
...
@@ -26,5 +35,12 @@ class TaggedItemSerializer(BasicModuleSerializer):
class
TaggedItemViewSet
(
BasicModuleViewSet
):
"""
Tagged item viewset
"""
def
extend_queryset
(
self
):
"""
Extend the queryset for a bit of optimization
"""
return
self
.
my_model_queryset
.
prefetch_related
(
"tag"
)
backend/backend_app/urls.py
View file @
7266140c
...
...
@@ -18,65 +18,54 @@ router = routers.DefaultRouter()
ALL_MODELS
=
[]
ALL_VIEWSETS
=
[]
# Automatically load
ing
models based on API config file
# Automatically load models
and viewset
based on API config file
api_config
=
get_api_config
()
for
model
in
api_config
:
model
=
DotMap
(
model
)
if
not
model
.
requires_testing
:
for
entry
in
api_config
:
model_obj
=
DotMap
(
entry
)
if
(
not
model_obj
.
requires_testing
)
or
(
settings
.
TESTING
and
model_obj
.
requires_testing
):
module
=
importlib
.
import_module
(
"backend_app.models.{}"
.
format
(
model
.
import_location
)
"backend_app.models.{}"
.
format
(
model
_obj
.
import_location
)
)
Viewset
=
getattr
(
module
,
model
.
viewset
)
Viewset
=
getattr
(
module
,
model
_obj
.
viewset
)
ALL_VIEWSETS
.
append
(
Viewset
)
if
model
.
model
is
not
None
and
not
model
.
ignore_in_admin
:
ALL_MODELS
.
append
(
getattr
(
module
,
model
.
model
))
# print(viewset)
str_url
=
model
.
api_end_point
if
"api_attr"
in
model
:
str_url
+=
"/{}"
.
format
(
model
.
api_attr
)
if
"api_name"
in
model
:
router
.
register
(
str_url
,
Viewset
,
model
.
api_name
)
else
:
router
.
register
(
str_url
,
Viewset
)
if
settings
.
TESTING
:
for
model
in
api_config
:
model
=
DotMap
(
model
)
if
model
.
requires_testing
:
module
=
importlib
.
import_module
(
"backend_app.models.{}"
.
format
(
model
.
import_location
)
)
Viewset
=
getattr
(
module
,
model
.
viewset
)
ALL_VIEWSETS
.
append
(
Viewset
)
if
model
.
model
is
not
None
:
ALL_MODELS
.
append
(
getattr
(
module
,
model
.
model
))
if
model_obj
.
model
is
not
None
and
not
model_obj
.
ignore_in_admin
:
ALL_MODELS
.
append
(
getattr
(
module
,
model_obj
.
model
))
str_url
=
model
.
api_end_point
if
"api_attr"
in
model
:
str_url
+=
"/{}"
.
format
(
model
.
api_attr
)
if
"api_name"
in
model
:
router
.
register
(
str_url
,
Viewset
,
model
.
api_name
)
# Creating the correct router entry
str_url
=
model_obj
.
api_end_point
if
"api_attr"
in
model_obj
:
str_url
+=
"/{}"
.
format
(
model_obj
.
api_attr
)
if
"api_name"
in
model_obj
:
router
.
register
(
str_url
,
Viewset
,
model_obj
.
api_name
)
else
:
router
.
register
(
str_url
,
Viewset
,
model
.
viewset
)
router
.
register
(
str_url
,
Viewset
)
# Add all the endpoints for the base api
urlpatterns
+=
[
url
(
r
"^api/"
,
include
(
router
.
urls
))]
# Add some custom APIs
urlpatterns
.
append
(
path
(
"api/serverModerationStatus/"
,
views
.
app_moderation_status
))
#######
# Models and Viewset checks
#######
# Check that all the models config have been set
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
(
m
odel
)
key
,
str
(
M
odel
)
)
)
# Check that all the viewsets have at least the permissions from the default viewset permissions
for
Viewset
in
ALL_VIEWSETS
:
for
p
in
DEFAULT_VIEWSET_PERMISSIONS
:
v_p
=
Viewset
.
permission_classes
...
...
backend/backend_app/utils/__get_model_config.py
View file @
7266140c
...
...
@@ -16,4 +16,4 @@ def get_model_config(model):
tmp
[
key
]
=
obj
[
key
]
return
tmp