Skip to content

Commit

Permalink
Update readonly field on serializers (#328)
Browse files Browse the repository at this point in the history
  • Loading branch information
kovacspe authored Dec 10, 2023
1 parent 6ee13a5 commit 6990bec
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 86 deletions.
43 changes: 43 additions & 0 deletions competition/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def can_user_participate(self, user):
>= self.min_years_until_graduation
return True

@classmethod
def can_user_create(cls, user: User, data): # pylint:disable=unused-argument
return user.is_authenticated and user.is_staff

def __str__(self):
return self.name

Expand Down Expand Up @@ -182,6 +186,11 @@ def is_active(self):
def can_user_modify(self, user):
return self.competition.can_user_modify(user)

@classmethod
def can_user_create(cls, user: User, data: dict) -> bool:
competition = Competition.objects.get(pk=data['competition'])
return competition.can_user_modify(user)

def can_user_participate(self, user):
return self.competition.can_user_participate(user)

Expand Down Expand Up @@ -228,6 +237,10 @@ def freeze_results(self, results):
raise FreezingNotClosedResults()
self.frozen_results = results

@property
def complete(self) -> bool:
return self.frozen_results is not None

@property
def is_active(self) -> bool:
return self.series_set.filter(complete=False).exists()
Expand Down Expand Up @@ -348,6 +361,11 @@ def num_problems(self) -> int:
def can_user_modify(self, user: User) -> bool:
return self.semester.can_user_modify(user)

@classmethod
def can_user_create(cls, user: User, data: dict) -> bool:
semester = Semester.objects.get(pk=data['semester'])
return semester.can_user_modify(user)

def can_user_participate(self, user: User) -> bool:
return self.semester.can_user_participate(user)

Expand Down Expand Up @@ -412,6 +430,11 @@ def num_corrected_solutions(self):
def can_user_modify(self, user):
return self.series.can_user_modify(user)

@classmethod
def can_user_create(cls, user: User, data: dict) -> bool:
series = Series.objects.get(pk=data['series'])
return series.can_user_modify(user)

def get_comments(self, user: User):
def filter_by_permissions(obj: 'Comment'):
if not user.is_anonymous and obj.can_user_modify(user):
Expand Down Expand Up @@ -488,6 +511,11 @@ def change_text(self, new_text):
def can_user_modify(self, user):
return self.problem.can_user_modify(user)

@classmethod
def can_user_create(cls, user: User, data: dict) -> bool:
problem = Problem.objects.get(pk=data['problem'])
return problem.can_user_modify(user)


class Grade(models.Model):
"""
Expand Down Expand Up @@ -567,6 +595,11 @@ def __str__(self):
def can_user_modify(self, user):
return self.event.can_user_modify(user)

@classmethod
def can_user_create(cls, user: User, data: dict) -> bool:
event = Event.objects.get(pk=data['event'])
return event.can_user_modify(user)


class Vote(models.IntegerChoices):
'''
Expand Down Expand Up @@ -629,6 +662,11 @@ def get_corrected_solution_file_name(self):
def can_user_modify(self, user):
return self.problem.can_user_modify(user)

@classmethod
def can_user_create(cls, user: User, data: dict) -> bool:
problem = Problem.objects.get(pk=data['problem'])
return problem.can_user_modify(user)

def set_vote(self, vote):
self.vote = vote
self.save()
Expand Down Expand Up @@ -684,6 +722,11 @@ def __str__(self):
def can_user_modify(self, user):
return self.event.can_user_modify(user)

@classmethod
def can_user_create(cls, user: User, data: dict) -> bool:
event = Event.objects.get(pk=data['event'])
return event.can_user_modify(user)


@receiver(post_save, sender=Publication)
def make_name_on_creation(sender, instance, created, **kwargs):
Expand Down
7 changes: 5 additions & 2 deletions competition/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ def has_object_permission(self, request, view, obj):

if view.action == 'retrieve':
if obj.state == CommentPublishState.PUBLISHED\
or can_user_modify\
or obj.posted_by == request.user:
or can_user_modify\
or obj.posted_by == request.user:
return True

if view.action in ['publish', 'hide']:
Expand Down Expand Up @@ -42,6 +42,9 @@ def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True

if request.method == 'POST':
return view.get_serializer().Meta.model.can_user_create(request.user, request.data)

return request.user.is_authenticated and request.user.is_staff

def has_object_permission(self, request, view, obj):
Expand Down
56 changes: 23 additions & 33 deletions competition/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,11 +292,10 @@ def get_series_num_solutions(self, obj):


@ts_interface(context='competition')
class SeriesWithProblemsSerializer(ModelWithParticipationSerializer):
class SeriesWithProblemsSerializer(serializers.ModelSerializer):
problems = ProblemSerializer(
many=True,
required=False,
allow_null=True
read_only=True
)
can_submit = serializers.SerializerMethodField('get_can_submit')
can_resubmit = serializers.SerializerMethodField('get_can_resubmit')
Expand All @@ -305,8 +304,10 @@ class SeriesWithProblemsSerializer(ModelWithParticipationSerializer):
class Meta:
model = models.Series
exclude = ['sum_method', 'frozen_results']
include = ['complete']
read_only_fields = ['semester', 'complete']
include = ['complete', 'problems', 'can_submit', 'can_resubmit']
read_only_fields = [
'complete',
'can_submit', 'can_resubmit']

def get_can_submit(self, obj):
return obj.can_submit
Expand All @@ -320,58 +321,47 @@ def get_can_resubmit(self, obj):
def get_event(self, obj):
return obj.semester

def create(self, validated_data):
problem_data = validated_data.pop('problems', [])
series = models.Series.objects.create(**validated_data)
for data in problem_data:
models.Problem.objects.create(series=series, **data)
return series


@ts_interface(context='competition')
class SemesterSerializer(serializers.ModelSerializer):
series_set = SeriesSerializer(
many=True,
required=False,
allow_null=True
read_only=True
)

class Meta:
model = models.Semester
fields = '__all__'

def create(self, validated_data):
series_data = validated_data.pop('series_set', [])
semester = models.Semester.objects.create(**validated_data)
for series in series_data:
models.Series.objects.create(semester=semester, **series)
return semester
read_only_fields = ['series_set', 'publication_set']


@ts_interface(context='competition')
class SemesterWithProblemsSerializer(ModelWithParticipationSerializer):
series_set = SeriesWithProblemsSerializer(
many=True,
required=False,
allow_null=True
read_only=True
)
publication_set = PublicationSerializer(many=True)
publication_set = PublicationSerializer(many=True, read_only=True)
complete = serializers.SerializerMethodField('get_complete')

class Meta:
model = models.Semester
fields = '__all__'
exclude = ['frozen_results', 'registration_link']
read_only_fields = ['complete']

def get_complete(self, obj: models.Semester):
return obj.complete

def update(self, instance: models.Semester, validated_data):
late_tags = validated_data.pop('late_tags', [])
instance.late_tags.clear()
for tag in late_tags:
instance.late_tags.add(tag)
return super().update(instance, validated_data)

def create(self, validated_data: dict):
all_series_data = validated_data.pop('series_set', [])
late_tags = validated_data.pop('late_tags', [])
validated_data.pop('publication_set', [])
semester = models.Semester.objects.create(**validated_data)
for series_data in all_series_data:
problems_data = series_data.pop('problems', [])
series = models.Series.objects.create(
semester=semester, **series_data)
for problem in problems_data:
models.Problem.objects.create(series=series, **problem)
for tag in late_tags:
semester.late_tags.add(tag)
return semester
Expand Down
62 changes: 14 additions & 48 deletions competition/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,21 @@ def test_permission_retrieve(self):

def test_permission_update(self):
self.check_permissions(self.URL_PREFIX + '/0/',
'PUT', self.ALL_FORBIDDEN, {'year': 2})
'PATCH', self.ONLY_STROM_OK_RESPONSES,
{
"semester": 1,
"order": 1,
"deadline": "2020-01-01T18:00:00Z"
})

def test_permission_create(self):
self.check_permissions(self.URL_PREFIX + '/',
'POST', self.ALL_FORBIDDEN, {'year': 2})
'POST', self.ONLY_STROM_OK_RESPONSES,
{
"semester": 1,
"order": 1,
"deadline": "2020-01-01T18:00:00Z"
})


class TestSemester(APITestCase, PermissionTestMixin):
Expand Down Expand Up @@ -172,9 +182,9 @@ def test_get_semster_result_specific(self):
def test_update_permissions(self):
''' update permission OK '''
self.check_permissions(self.URL_PREFIX + '/0/',
'PUT', self.ALL_FORBIDDEN, {'year': 2})
'PATCH', self.ONLY_STROM_OK_RESPONSES, {'year': 2})
self.check_permissions(self.URL_PREFIX + '/10/',
'PUT', self.ALL_FORBIDDEN, {'year': 2})
'PATCH', self.ONLY_KRICKY_OK_RESPONSES, {'year': 2})

def test_public_points_permission(self):
self.check_permissions(self.URL_PREFIX + '/',
Expand Down Expand Up @@ -214,48 +224,6 @@ def setUp(self):

def test_create_semester(self):
data = {
"series_set": [
{
"problems": [
{
"text": "2+2",
"order": 1
},
{
"text": "3+3",
"order": 2
},
{
"text": "3+4",
"order": 3
}
],
"order": 2,
"deadline": "2017-11-19T22:00:00Z",
"complete": False
},
{
"problems": [
{
"text": """$EFGH$ je rovnobežník s ostrým uhlom $DAB$.
Dokážte, že $AD$ a $DO$ sú na seba kolmé.""",
"order": 1
},
{
"text": """$IJKL$ je rovnobežník s ostrým uhlom $DAB$.
Body $A,\\ P,\\ B,\\ D$ ležia na jednej kružnici v tomto poradí.
Priamky $AP$ a $CD$ sa pretínajú v bode $Q$. Bod $O$ je stred kružnice
opísanej trojuholníku $CPQ$. Dokážte, že ak $D \\neq O$,
tak priamky $AD$ a $DO$ sú na seba kolmé.""",
"order": 2
}
],
"order": 2,
"deadline": "2017-11-19T22:00:00Z",
"complete": False
}
],
"publication_set": [],
"year": 42,
"school_year": "2017/2018",
"start": "2017-10-02T22:00:00Z",
Expand All @@ -268,8 +236,6 @@ def test_create_semester(self):
'POST',
self.ONLY_STROM_OK_RESPONSES, data)
self.assertEqual(models.Semester.objects.count(), 2)
self.assertEqual(models.Series.objects.count(), 2)
self.assertEqual(models.Problem.objects.count(), 5)


class TestCompetition(APITestCase, PermissionTestMixin):
Expand Down
4 changes: 2 additions & 2 deletions competition/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ class SeriesViewSet(ModelViewSetWithSerializerContext):
queryset = Series.objects.all()
serializer_class = SeriesWithProblemsSerializer
permission_classes = (CompetitionRestrictedPermission,)
http_method_names = ['get', 'head']
http_method_names = ['get', 'head', 'put', 'patch', "post"]

@staticmethod
def __create_result_json(series: Series) -> dict:
Expand Down Expand Up @@ -610,7 +610,7 @@ class SemesterViewSet(ModelViewSetWithSerializerContext):
serializer_class = SemesterWithProblemsSerializer
permission_classes = (CompetitionRestrictedPermission,)
filterset_fields = ['competition']
http_method_names = ['get', 'post', 'head']
http_method_names = ['get', 'head', 'put', 'patch', 'post']

def perform_create(self, serializer):
"""
Expand Down
5 changes: 4 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path

from rest_framework.test import APIClient

from user.models import User
from webstrom.settings import BASE_DIR

Expand Down Expand Up @@ -71,7 +72,7 @@ def get_client(self, user_type=None):
self.client.force_authenticate(user=user)
return self.client

#pylint: disable=dangerous-default-value
# pylint: disable=dangerous-default-value
def check_permissions(self, url, method, responses, body={}):
for user, status in responses.items():
client = self.get_client(user)
Expand All @@ -83,6 +84,8 @@ def check_permissions(self, url, method, responses, body={}):
response = client.post(url, body, 'json')
elif method == 'PUT':
response = client.put(url, body, 'json')
elif method == 'PATCH':
response = client.patch(url, body, 'json')
else:
raise NotImplementedError()
self.assertIn(response.status_code, expected_response,
Expand Down

0 comments on commit 6990bec

Please sign in to comment.