Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update readonly field on serializers #328

Merged
merged 6 commits into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading