From e99c1aeb56fe9b43bb7847446cb2d6f42f2cc2d5 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Mon, 16 Dec 2024 16:31:32 +0100 Subject: [PATCH 01/31] first commit --- oioioi/base/admin.py | 11 +++++-- oioioi/base/forms.py | 2 ++ oioioi/base/migrations/0005_permissions.py | 19 ++++++++++++ .../migrations/0006_delete_permissions.py | 16 ++++++++++ oioioi/problems/admin.py | 27 +++++++++++------ .../migrations/0032_alter_problem_options.py | 17 +++++++++++ .../migrations/0033_alter_problem_options.py | 17 +++++++++++ .../migrations/0034_alter_problem_options.py | 17 +++++++++++ .../migrations/0035_alter_problem_options.py | 17 +++++++++++ oioioi/problems/models.py | 1 + oioioi/problems/problem_site.py | 24 +++++++++++++++ .../problems/templates/problems/settings.html | 23 --------------- oioioi/problems/templates/problems/tags.html | 29 +++++++++++++++++++ oioioi/problems/utils.py | 10 +++++++ 14 files changed, 196 insertions(+), 34 deletions(-) create mode 100644 oioioi/base/migrations/0005_permissions.py create mode 100644 oioioi/base/migrations/0006_delete_permissions.py create mode 100644 oioioi/problems/migrations/0032_alter_problem_options.py create mode 100644 oioioi/problems/migrations/0033_alter_problem_options.py create mode 100644 oioioi/problems/migrations/0034_alter_problem_options.py create mode 100644 oioioi/problems/migrations/0035_alter_problem_options.py create mode 100644 oioioi/problems/templates/problems/tags.html diff --git a/oioioi/base/admin.py b/oioioi/base/admin.py index a515a311a..10a824157 100644 --- a/oioioi/base/admin.py +++ b/oioioi/base/admin.py @@ -15,6 +15,7 @@ from django.utils.html import escape from django.utils.text import capfirst from django.utils.translation import gettext_lazy as _ +from django import forms from oioioi.base.forms import OioioiUserChangeForm, OioioiUserCreationForm from oioioi.base.menu import MenuRegistry, side_pane_menus_registry @@ -356,14 +357,20 @@ class OioioiUserAdmin(UserAdmin, ObjectWithMixins, metaclass=ModelAdminMeta): fieldsets = ( (None, {'fields': ('username', 'password')}), (_("Personal info"), {'fields': ('first_name', 'last_name', 'email')}), - (_("Permissions"), {'fields': ('is_active', 'is_superuser', 'groups')}), + (_("Permissions"), {'fields': ('is_active', 'is_superuser', 'user_permissions', 'groups')}), (_("Important dates"), {'fields': ('last_login', 'date_joined')}), ) list_filter = ['is_superuser', 'is_active'] list_display = ['username', 'email', 'first_name', 'last_name', 'is_active'] - filter_horizontal = () + filter_horizontal = ('user_permissions',) actions = ['activate_user'] + # Overriding the formfield_for_manytomany method to ensure we render the field as checkboxes + def formfield_for_manytomany(self, db_field, request=None, **kwargs): + if db_field.name == 'user_permissions': + kwargs['widget'] = forms.CheckboxSelectMultiple() + return super().formfield_for_manytomany(db_field, request, **kwargs) + def activate_user(self, request, qs): qs.update(is_active=True) diff --git a/oioioi/base/forms.py b/oioioi/base/forms.py index ce937c622..0307278d1 100644 --- a/oioioi/base/forms.py +++ b/oioioi/base/forms.py @@ -2,6 +2,7 @@ import bleach from collections import OrderedDict +from django.contrib.auth.models import Permission from captcha.fields import CaptchaField, CaptchaTextInput from django import forms from django.conf import settings @@ -269,6 +270,7 @@ def __init__(self, *args, **kwargs): super(OioioiUserChangeForm, self).__init__(*args, **kwargs) adjust_username_field(self) adjust_name_fields(self) + self.fields['user_permissions'].queryset = Permission.objects.filter(codename='can_add_tags') class OioioiPasswordResetForm(PasswordResetForm): diff --git a/oioioi/base/migrations/0005_permissions.py b/oioioi/base/migrations/0005_permissions.py new file mode 100644 index 000000000..f6cfa1759 --- /dev/null +++ b/oioioi/base/migrations/0005_permissions.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.16 on 2024-12-04 14:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0004_alter_userpreferences_enable_editor'), + ] + + operations = [ + migrations.CreateModel( + name='Permissions', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + ] diff --git a/oioioi/base/migrations/0006_delete_permissions.py b/oioioi/base/migrations/0006_delete_permissions.py new file mode 100644 index 000000000..4bbf3b9c1 --- /dev/null +++ b/oioioi/base/migrations/0006_delete_permissions.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2.16 on 2024-12-04 14:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0005_permissions'), + ] + + operations = [ + migrations.DeleteModel( + name='Permissions', + ), + ] diff --git a/oioioi/problems/admin.py b/oioioi/problems/admin.py index e56b6a149..34063d3f6 100644 --- a/oioioi/problems/admin.py +++ b/oioioi/problems/admin.py @@ -284,7 +284,7 @@ class BaseTagAdmin(admin.ModelAdmin): form=OriginTagThroughForm, verbose_name=_("origin tag"), verbose_name_plural=_("origin tags"), - has_permission_func=lambda self, request, obj=None: request.user.is_superuser, + has_permission_func=lambda self, request, obj=None: request.user.is_superuser or request.user.has_perm("problems.can_add_tags"), ) class OriginTagInline(admin.StackedInline): pass @@ -324,7 +324,7 @@ class OriginInfoCategoryAdmin(admin.ModelAdmin): form=OriginInfoValueThroughForm, verbose_name=_("origin information"), verbose_name_plural=_("additional origin information"), - has_permission_func=lambda self, request, obj=None: request.user.is_superuser, + has_permission_func=lambda self, request, obj=None: request.user.is_superuser or request.user.has_perm("problems.can_add_tags"), ) class OriginInfoValueInline(admin.StackedInline): pass @@ -403,6 +403,10 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): admin.site.register(AlgorithmTag, AlgorithmTagAdmin) +def isTagInline(inline): + return inline in (AlgorithmTagInline, OriginTagInline, DifficultyTagInline, OriginInfoValueInline) + + class ProblemAdmin(admin.ModelAdmin): inlines = ( DifficultyTagInline, @@ -437,10 +441,10 @@ def has_change_permission(self, request, obj=None): if obj is None: return self.get_queryset(request).exists() else: - return can_admin_problem(request, obj) + return can_admin_problem(request, obj) or request.user.has_perm("problems.can_add_tags") def has_delete_permission(self, request, obj=None): - return self.has_change_permission(request, obj) + return can_admin_problem(request, obj) def redirect_to_list(self, request, problem): if problem.contest: @@ -480,7 +484,7 @@ def get_queryset(self, request): combined = queryset.none() else: combined = request.user.problem_set.all() - if request.user.has_perm('problems.problems_db_admin'): + if request.user.has_perm('problems.problems_db_admin') or request.user.has_perm('problems.can_add_tags'): combined |= queryset.filter(contest__isnull=True) if is_contest_basicadmin(request): combined |= queryset.filter(contest=request.contest) @@ -502,10 +506,15 @@ def get_readonly_fields(self, request, obj=None): def change_view(self, request, object_id, form_url='', extra_context=None): extra_context = extra_context or {} - extra_context['categories'] = sorted( - set([getattr(inline, 'category', None) for inline in self.inlines]) - ) - extra_context['no_category'] = NO_CATEGORY + extra_context['categories'] = set() + for inline in self.inlines: + if request.user.is_superuser or request.user.has_perm('problems.problems_db_admin'): + extra_context['categories'].add(getattr(inline, 'category', None)) + elif isTagInline(inline) and request.user.has_perm("problems.can_add_tags"): + extra_context['categories'].add(getattr(inline, 'category', None)) + + if request.user.is_superuser or request.user.has_perm('problems.problems_db_admin'): + extra_context['no_category'] = NO_CATEGORY return super(ProblemAdmin, self).change_view( request, object_id, form_url, extra_context=extra_context ) diff --git a/oioioi/problems/migrations/0032_alter_problem_options.py b/oioioi/problems/migrations/0032_alter_problem_options.py new file mode 100644 index 000000000..96b7586dc --- /dev/null +++ b/oioioi/problems/migrations/0032_alter_problem_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.16 on 2024-12-04 14:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('problems', '0031_auto_20220328_1124'), + ] + + operations = [ + migrations.AlterModelOptions( + name='problem', + options={'permissions': (('tagger', 'Can add and modify tags'), ('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, + ), + ] diff --git a/oioioi/problems/migrations/0033_alter_problem_options.py b/oioioi/problems/migrations/0033_alter_problem_options.py new file mode 100644 index 000000000..750c775c8 --- /dev/null +++ b/oioioi/problems/migrations/0033_alter_problem_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.16 on 2024-12-04 14:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('problems', '0032_alter_problem_options'), + ] + + operations = [ + migrations.AlterModelOptions( + name='problem', + options={'permissions': (('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, + ), + ] diff --git a/oioioi/problems/migrations/0034_alter_problem_options.py b/oioioi/problems/migrations/0034_alter_problem_options.py new file mode 100644 index 000000000..2727eecce --- /dev/null +++ b/oioioi/problems/migrations/0034_alter_problem_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.16 on 2024-12-04 14:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('problems', '0033_alter_problem_options'), + ] + + operations = [ + migrations.AlterModelOptions( + name='problem', + options={'permissions': (('tagger', 'Can add and modify tags'), ('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, + ), + ] diff --git a/oioioi/problems/migrations/0035_alter_problem_options.py b/oioioi/problems/migrations/0035_alter_problem_options.py new file mode 100644 index 000000000..169847144 --- /dev/null +++ b/oioioi/problems/migrations/0035_alter_problem_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.16 on 2024-12-04 15:09 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('problems', '0034_alter_problem_options'), + ] + + operations = [ + migrations.AlterModelOptions( + name='problem', + options={'permissions': (('can_add_tags', 'Can add tags'), ('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, + ), + ] diff --git a/oioioi/problems/models.py b/oioioi/problems/models.py index 9c09a0bee..31ad11edd 100644 --- a/oioioi/problems/models.py +++ b/oioioi/problems/models.py @@ -155,6 +155,7 @@ class Meta(object): verbose_name = _("problem") verbose_name_plural = _("problems") permissions = ( + ('can_add_tags', _("Can add tags")), ('problems_db_admin', _("Can administer the problems database")), ('problem_admin', _("Can administer the problem")), ) diff --git a/oioioi/problems/problem_site.py b/oioioi/problems/problem_site.py index 4c3ca539d..39abd563c 100644 --- a/oioioi/problems/problem_site.py +++ b/oioioi/problems/problem_site.py @@ -35,6 +35,7 @@ ) from oioioi.problems.problem_sources import UploadedPackageSource from oioioi.problems.utils import ( + can_add_tags, can_admin_problem, generate_add_to_contest_metadata, generate_model_solutions_context, @@ -272,6 +273,29 @@ def problem_site_settings(request, problem): }, ) +@problem_site_tab(_("Tags"), key='tags', order=600, condition=can_add_tags) +def problem_site_tags(request, problem): + package = ProblemPackage.objects.filter(problem=problem).first() + algorithm_tag_proposals = ( + AlgorithmTagProposal.objects.all().filter(problem=problem).order_by('-pk')[:25] + ) + difficulty_tag_proposals = ( + DifficultyTagProposal.objects.all().filter(problem=problem).order_by('-pk')[:25] + ) + + return TemplateResponse( + request, + 'problems/tags.html', + { + 'site_key': problem.problemsite.url_key, + 'problem': problem, + 'package': package if package and package.package_file else None, + 'algorithm_tag_proposals': algorithm_tag_proposals, + 'difficulty_tag_proposals': difficulty_tag_proposals, + 'can_admin_problem': can_admin_problem(request, problem), + }, + ) + @problem_site_tab(_("Add to contest"), key='add_to_contest', order=700) def problem_site_add_to_contest(request, problem): diff --git a/oioioi/problems/templates/problems/settings.html b/oioioi/problems/templates/problems/settings.html index c4558d007..53717d9b8 100644 --- a/oioioi/problems/templates/problems/settings.html +++ b/oioioi/problems/templates/problems/settings.html @@ -8,11 +8,6 @@ {% include 'problems/ingredients/add-to-contest-panel.html' %} - {# Tags panel #} -
- {% include 'problems/ingredients/tags-panel.html' %} -
- {# Management buttons #}
{% include 'problems/ingredients/action-btn-panel.html' %} @@ -34,15 +29,6 @@
-
- - {# Tags panel #} -
- {% include 'problems/ingredients/tags-panel.html' %} -
- -
-
{# Management buttons #} @@ -61,15 +47,6 @@
-
- - {# Tags panel #} -
- {% include 'problems/ingredients/tags-panel.html' %} -
- -
-
{# Model solutions panel #} diff --git a/oioioi/problems/templates/problems/tags.html b/oioioi/problems/templates/problems/tags.html new file mode 100644 index 000000000..38ee03fba --- /dev/null +++ b/oioioi/problems/templates/problems/tags.html @@ -0,0 +1,29 @@ +{% load i18n %} + + +
+ + {# Tags panel #} +
+ {% include 'problems/ingredients/tags-panel.html' %} +
+ +
+ +
+ + {# Tags panel #} +
+ {% include 'problems/ingredients/tags-panel.html' %} +
+ +
+ +
+ + {# Tags panel #} +
+ {% include 'problems/ingredients/tags-panel.html' %} +
+ +
diff --git a/oioioi/problems/utils.py b/oioioi/problems/utils.py index a33469080..ecc0807d8 100644 --- a/oioioi/problems/utils.py +++ b/oioioi/problems/utils.py @@ -71,6 +71,16 @@ def can_admin_problem(request, problem): return False +def can_add_tags(request, problem): + """Checks if the user can add tags to the problem. + + The user can admin the given problem if user can admin problem or user is a tagger + + The caller should guarantee that any of the given arguments is not None. + """ + return request.user.has_perm('problems.can_add_tags') or can_admin_problem(request, problem) + + def can_admin_instance_of_problem(request, problem): """Checks if the user has admin permission in a ProblemInstace of the given Problem. From bcd30a3a13190937eee3430e8b2aac621ab349f4 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Mon, 16 Dec 2024 17:47:20 +0100 Subject: [PATCH 02/31] fix permissions --- oioioi/problems/admin.py | 24 ++++++++++++++++++++++-- oioioi/problems/problem_site.py | 10 ---------- oioioi/problems/utils.py | 2 +- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/oioioi/problems/admin.py b/oioioi/problems/admin.py index 34063d3f6..22261c722 100644 --- a/oioioi/problems/admin.py +++ b/oioioi/problems/admin.py @@ -274,10 +274,28 @@ def _update_queryset_if_problems(db_field, **kwargs): class BaseTagLocalizationInline(admin.StackedInline): formset = LocalizationFormset + def has_add_permission(self, request, obj=None): + return True + + def has_change_permission(self, request, obj=None): + return True + + def has_delete_permission(self, request, obj=None): + return True + class BaseTagAdmin(admin.ModelAdmin): filter_horizontal = ('problems',) + def has_add_permission(self, request, obj=None): + return True + + def has_change_permission(self, request, obj=None): + return True + + def has_delete_permission(self, request, obj=None): + return True + @tag_inline( model=OriginTag.problems.through, @@ -354,6 +372,7 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): form=DifficultyTagThroughForm, verbose_name=_("Difficulty Tag"), verbose_name_plural=_("Difficulty Tags"), + has_permission_func=lambda self, request, obj=None: request.user.is_superuser or request.user.has_perm("problems.can_add_tags"), ) class DifficultyTagInline(admin.StackedInline): pass @@ -381,6 +400,7 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): form=AlgorithmTagThroughForm, verbose_name=_("Algorithm Tag"), verbose_name_plural=_("Algorithm Tags"), + has_permission_func=lambda self, request, obj=None: request.user.is_superuser or request.user.has_perm("problems.can_add_tags"), ) class AlgorithmTagInline(admin.StackedInline): pass @@ -403,7 +423,7 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): admin.site.register(AlgorithmTag, AlgorithmTagAdmin) -def isTagInline(inline): +def isInlineTag(inline): return inline in (AlgorithmTagInline, OriginTagInline, DifficultyTagInline, OriginInfoValueInline) @@ -510,7 +530,7 @@ def change_view(self, request, object_id, form_url='', extra_context=None): for inline in self.inlines: if request.user.is_superuser or request.user.has_perm('problems.problems_db_admin'): extra_context['categories'].add(getattr(inline, 'category', None)) - elif isTagInline(inline) and request.user.has_perm("problems.can_add_tags"): + elif isInlineTag(inline) and request.user.has_perm("problems.can_add_tags"): extra_context['categories'].add(getattr(inline, 'category', None)) if request.user.is_superuser or request.user.has_perm('problems.problems_db_admin'): diff --git a/oioioi/problems/problem_site.py b/oioioi/problems/problem_site.py index 39abd563c..bcb1362d3 100644 --- a/oioioi/problems/problem_site.py +++ b/oioioi/problems/problem_site.py @@ -250,12 +250,6 @@ def problem_site_settings(request, problem): ) model_solutions = generate_model_solutions_context(request, problem_instance) extra_actions = problem.controller.get_extra_problem_site_actions(problem) - algorithm_tag_proposals = ( - AlgorithmTagProposal.objects.all().filter(problem=problem).order_by('-pk')[:25] - ) - difficulty_tag_proposals = ( - DifficultyTagProposal.objects.all().filter(problem=problem).order_by('-pk')[:25] - ) return TemplateResponse( request, @@ -266,8 +260,6 @@ def problem_site_settings(request, problem): 'administered_recent_contests': administered_recent_contests, 'package': package if package and package.package_file else None, 'model_solutions': model_solutions, - 'algorithm_tag_proposals': algorithm_tag_proposals, - 'difficulty_tag_proposals': difficulty_tag_proposals, 'can_admin_problem': can_admin_problem(request, problem), 'extra_actions': extra_actions, }, @@ -275,7 +267,6 @@ def problem_site_settings(request, problem): @problem_site_tab(_("Tags"), key='tags', order=600, condition=can_add_tags) def problem_site_tags(request, problem): - package = ProblemPackage.objects.filter(problem=problem).first() algorithm_tag_proposals = ( AlgorithmTagProposal.objects.all().filter(problem=problem).order_by('-pk')[:25] ) @@ -289,7 +280,6 @@ def problem_site_tags(request, problem): { 'site_key': problem.problemsite.url_key, 'problem': problem, - 'package': package if package and package.package_file else None, 'algorithm_tag_proposals': algorithm_tag_proposals, 'difficulty_tag_proposals': difficulty_tag_proposals, 'can_admin_problem': can_admin_problem(request, problem), diff --git a/oioioi/problems/utils.py b/oioioi/problems/utils.py index ecc0807d8..41cc72292 100644 --- a/oioioi/problems/utils.py +++ b/oioioi/problems/utils.py @@ -74,7 +74,7 @@ def can_admin_problem(request, problem): def can_add_tags(request, problem): """Checks if the user can add tags to the problem. - The user can admin the given problem if user can admin problem or user is a tagger + The user can admin the given problem if user can admin problem or user has can_add_tags permission The caller should guarantee that any of the given arguments is not None. """ From d96ba08a463d5a43e312405ded04d7f48a04fc9d Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Sun, 22 Dec 2024 20:45:00 +0100 Subject: [PATCH 03/31] fix admin panel not saving --- oioioi/problems/admin.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/oioioi/problems/admin.py b/oioioi/problems/admin.py index 22261c722..903cae08f 100644 --- a/oioioi/problems/admin.py +++ b/oioioi/problems/admin.py @@ -423,11 +423,13 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): admin.site.register(AlgorithmTag, AlgorithmTagAdmin) -def isInlineTag(inline): - return inline in (AlgorithmTagInline, OriginTagInline, DifficultyTagInline, OriginInfoValueInline) - - class ProblemAdmin(admin.ModelAdmin): + tag_inlines = ( + DifficultyTagInline, + AlgorithmTagInline, + OriginTagInline, + OriginInfoValueInline, + ) inlines = ( DifficultyTagInline, AlgorithmTagInline, @@ -526,18 +528,20 @@ def get_readonly_fields(self, request, obj=None): def change_view(self, request, object_id, form_url='', extra_context=None): extra_context = extra_context or {} - extra_context['categories'] = set() - for inline in self.inlines: - if request.user.is_superuser or request.user.has_perm('problems.problems_db_admin'): - extra_context['categories'].add(getattr(inline, 'category', None)) - elif isInlineTag(inline) and request.user.has_perm("problems.can_add_tags"): - extra_context['categories'].add(getattr(inline, 'category', None)) - + extra_context['categories'] = sorted( + set([getattr(inline, 'category', None) for inline in self.get_inlines(request, None)]) + ) if request.user.is_superuser or request.user.has_perm('problems.problems_db_admin'): extra_context['no_category'] = NO_CATEGORY return super(ProblemAdmin, self).change_view( request, object_id, form_url, extra_context=extra_context ) + def get_inlines(self, request, obj): + if request.user.is_superuser: + return super().get_inlines(request, obj) + self.inlines + elif request.user.has_perm('problems.can_add_tags'): + return self.tag_inlines + class BaseProblemAdmin(admin.MixinsAdmin): From d3674901e39a81c774a26823f4b0339eb16c8e91 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Sun, 22 Dec 2024 20:52:15 +0100 Subject: [PATCH 04/31] small fixes --- oioioi/problems/admin.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/oioioi/problems/admin.py b/oioioi/problems/admin.py index 903cae08f..627baec65 100644 --- a/oioioi/problems/admin.py +++ b/oioioi/problems/admin.py @@ -275,26 +275,26 @@ class BaseTagLocalizationInline(admin.StackedInline): formset = LocalizationFormset def has_add_permission(self, request, obj=None): - return True + return request.user.has_perm("problems.can_add_tags") def has_change_permission(self, request, obj=None): - return True - + return request.user.has_perm("problems.can_add_tags") + def has_delete_permission(self, request, obj=None): - return True + return request.user.has_perm("problems.can_add_tags") class BaseTagAdmin(admin.ModelAdmin): filter_horizontal = ('problems',) def has_add_permission(self, request, obj=None): - return True + return request.user.has_perm("problems.can_add_tags") def has_change_permission(self, request, obj=None): - return True + return request.user.has_perm("problems.can_add_tags") def has_delete_permission(self, request, obj=None): - return True + return request.user.has_perm("problems.can_add_tags") @tag_inline( @@ -537,10 +537,12 @@ def change_view(self, request, object_id, form_url='', extra_context=None): request, object_id, form_url, extra_context=extra_context ) def get_inlines(self, request, obj): - if request.user.is_superuser: + if request.user.is_superuser or request.user.has_perm('problems.problems_db_admin'): return super().get_inlines(request, obj) + self.inlines elif request.user.has_perm('problems.can_add_tags'): return self.tag_inlines + else: + return () From 47ff36b9e6fd609ec469e295f40bde2f0ba669ef Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Sun, 22 Dec 2024 21:00:45 +0100 Subject: [PATCH 05/31] fix origin info --- oioioi/problems/admin.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/oioioi/problems/admin.py b/oioioi/problems/admin.py index 627baec65..c317c9e3f 100644 --- a/oioioi/problems/admin.py +++ b/oioioi/problems/admin.py @@ -362,7 +362,15 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): return super(OriginInfoValueAdmin, self).formfield_for_manytomany( db_field, request, **kwargs ) + + def has_add_permission(self, request, obj=None): + return request.user.has_perm("problems.can_add_tags") + + def has_change_permission(self, request, obj=None): + return request.user.has_perm("problems.can_add_tags") + def has_delete_permission(self, request, obj=None): + return request.user.has_perm("problems.can_add_tags") admin.site.register(OriginInfoValue, OriginInfoValueAdmin) From 170aff1bfd3ddabfb48ed425866913bfcbb12f90 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Fri, 27 Dec 2024 12:14:45 +0100 Subject: [PATCH 06/31] change permission name --- oioioi/base/forms.py | 2 +- oioioi/problems/admin.py | 34 +++++++++---------- .../migrations/0036_alter_problem_options.py | 17 ++++++++++ oioioi/problems/models.py | 2 +- oioioi/problems/problem_site.py | 4 +-- oioioi/problems/utils.py | 12 ++++--- 6 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 oioioi/problems/migrations/0036_alter_problem_options.py diff --git a/oioioi/base/forms.py b/oioioi/base/forms.py index 0307278d1..a512b84e8 100644 --- a/oioioi/base/forms.py +++ b/oioioi/base/forms.py @@ -270,7 +270,7 @@ def __init__(self, *args, **kwargs): super(OioioiUserChangeForm, self).__init__(*args, **kwargs) adjust_username_field(self) adjust_name_fields(self) - self.fields['user_permissions'].queryset = Permission.objects.filter(codename='can_add_tags') + self.fields['user_permissions'].queryset = Permission.objects.filter(codename='can_modify_tags') class OioioiPasswordResetForm(PasswordResetForm): diff --git a/oioioi/problems/admin.py b/oioioi/problems/admin.py index c317c9e3f..9f293c7e0 100644 --- a/oioioi/problems/admin.py +++ b/oioioi/problems/admin.py @@ -55,7 +55,7 @@ ProblemSite, ProblemStatement, ) -from oioioi.problems.utils import can_add_problems, can_admin_problem +from oioioi.problems.utils import can_add_problems, can_admin_problem, can_modify_tags logger = logging.getLogger(__name__) @@ -275,26 +275,26 @@ class BaseTagLocalizationInline(admin.StackedInline): formset = LocalizationFormset def has_add_permission(self, request, obj=None): - return request.user.has_perm("problems.can_add_tags") + return can_modify_tags(request, obj) def has_change_permission(self, request, obj=None): - return request.user.has_perm("problems.can_add_tags") + return can_modify_tags(request, obj) def has_delete_permission(self, request, obj=None): - return request.user.has_perm("problems.can_add_tags") + return can_modify_tags(request, obj) class BaseTagAdmin(admin.ModelAdmin): filter_horizontal = ('problems',) def has_add_permission(self, request, obj=None): - return request.user.has_perm("problems.can_add_tags") + return can_modify_tags(request, obj) def has_change_permission(self, request, obj=None): - return request.user.has_perm("problems.can_add_tags") + return can_modify_tags(request, obj) def has_delete_permission(self, request, obj=None): - return request.user.has_perm("problems.can_add_tags") + return can_modify_tags(request, obj) @tag_inline( @@ -302,7 +302,7 @@ def has_delete_permission(self, request, obj=None): form=OriginTagThroughForm, verbose_name=_("origin tag"), verbose_name_plural=_("origin tags"), - has_permission_func=lambda self, request, obj=None: request.user.is_superuser or request.user.has_perm("problems.can_add_tags"), + has_permission_func=lambda self, request, obj=None: can_modify_tags(request, obj) ) class OriginTagInline(admin.StackedInline): pass @@ -342,7 +342,7 @@ class OriginInfoCategoryAdmin(admin.ModelAdmin): form=OriginInfoValueThroughForm, verbose_name=_("origin information"), verbose_name_plural=_("additional origin information"), - has_permission_func=lambda self, request, obj=None: request.user.is_superuser or request.user.has_perm("problems.can_add_tags"), + has_permission_func=lambda self, request, obj=None: can_modify_tags(request, obj), ) class OriginInfoValueInline(admin.StackedInline): pass @@ -364,13 +364,13 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): ) def has_add_permission(self, request, obj=None): - return request.user.has_perm("problems.can_add_tags") + return can_modify_tags(request, obj) def has_change_permission(self, request, obj=None): - return request.user.has_perm("problems.can_add_tags") + return can_modify_tags(request, obj) def has_delete_permission(self, request, obj=None): - return request.user.has_perm("problems.can_add_tags") + return can_modify_tags(request, obj) admin.site.register(OriginInfoValue, OriginInfoValueAdmin) @@ -380,7 +380,7 @@ def has_delete_permission(self, request, obj=None): form=DifficultyTagThroughForm, verbose_name=_("Difficulty Tag"), verbose_name_plural=_("Difficulty Tags"), - has_permission_func=lambda self, request, obj=None: request.user.is_superuser or request.user.has_perm("problems.can_add_tags"), + has_permission_func=lambda self, request, obj=None: can_modify_tags(request, obj), ) class DifficultyTagInline(admin.StackedInline): pass @@ -408,7 +408,7 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): form=AlgorithmTagThroughForm, verbose_name=_("Algorithm Tag"), verbose_name_plural=_("Algorithm Tags"), - has_permission_func=lambda self, request, obj=None: request.user.is_superuser or request.user.has_perm("problems.can_add_tags"), + has_permission_func=lambda self, request, obj=None: can_modify_tags(request, obj), ) class AlgorithmTagInline(admin.StackedInline): pass @@ -471,7 +471,7 @@ def has_change_permission(self, request, obj=None): if obj is None: return self.get_queryset(request).exists() else: - return can_admin_problem(request, obj) or request.user.has_perm("problems.can_add_tags") + return can_modify_tags(request, obj) def has_delete_permission(self, request, obj=None): return can_admin_problem(request, obj) @@ -514,7 +514,7 @@ def get_queryset(self, request): combined = queryset.none() else: combined = request.user.problem_set.all() - if request.user.has_perm('problems.problems_db_admin') or request.user.has_perm('problems.can_add_tags'): + if request.user.has_perm('problems.problems_db_admin') or can_modify_tags(request, None): combined |= queryset.filter(contest__isnull=True) if is_contest_basicadmin(request): combined |= queryset.filter(contest=request.contest) @@ -547,7 +547,7 @@ def change_view(self, request, object_id, form_url='', extra_context=None): def get_inlines(self, request, obj): if request.user.is_superuser or request.user.has_perm('problems.problems_db_admin'): return super().get_inlines(request, obj) + self.inlines - elif request.user.has_perm('problems.can_add_tags'): + elif can_modify_tags(request, obj): return self.tag_inlines else: return () diff --git a/oioioi/problems/migrations/0036_alter_problem_options.py b/oioioi/problems/migrations/0036_alter_problem_options.py new file mode 100644 index 000000000..c4d891947 --- /dev/null +++ b/oioioi/problems/migrations/0036_alter_problem_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.16 on 2024-12-24 11:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('problems', '0035_alter_problem_options'), + ] + + operations = [ + migrations.AlterModelOptions( + name='problem', + options={'permissions': (('can_modify_tags', 'Can modify tags'), ('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, + ), + ] diff --git a/oioioi/problems/models.py b/oioioi/problems/models.py index 31ad11edd..f8241db28 100644 --- a/oioioi/problems/models.py +++ b/oioioi/problems/models.py @@ -155,7 +155,7 @@ class Meta(object): verbose_name = _("problem") verbose_name_plural = _("problems") permissions = ( - ('can_add_tags', _("Can add tags")), + ('can_modify_tags', _("Can modify tags")), ('problems_db_admin', _("Can administer the problems database")), ('problem_admin', _("Can administer the problem")), ) diff --git a/oioioi/problems/problem_site.py b/oioioi/problems/problem_site.py index bcb1362d3..c361491f2 100644 --- a/oioioi/problems/problem_site.py +++ b/oioioi/problems/problem_site.py @@ -35,7 +35,7 @@ ) from oioioi.problems.problem_sources import UploadedPackageSource from oioioi.problems.utils import ( - can_add_tags, + can_modify_tags, can_admin_problem, generate_add_to_contest_metadata, generate_model_solutions_context, @@ -265,7 +265,7 @@ def problem_site_settings(request, problem): }, ) -@problem_site_tab(_("Tags"), key='tags', order=600, condition=can_add_tags) +@problem_site_tab(_("Tags"), key='tags', order=600, condition=can_modify_tags) def problem_site_tags(request, problem): algorithm_tag_proposals = ( AlgorithmTagProposal.objects.all().filter(problem=problem).order_by('-pk')[:25] diff --git a/oioioi/problems/utils.py b/oioioi/problems/utils.py index 41cc72292..3f7fd17fc 100644 --- a/oioioi/problems/utils.py +++ b/oioioi/problems/utils.py @@ -71,14 +71,16 @@ def can_admin_problem(request, problem): return False -def can_add_tags(request, problem): +def can_modify_tags(request, problem): """Checks if the user can add tags to the problem. - The user can admin the given problem if user can admin problem or user has can_add_tags permission - - The caller should guarantee that any of the given arguments is not None. + The user can modify tags if user can admin problem or user has can_modify_tags permission """ - return request.user.has_perm('problems.can_add_tags') or can_admin_problem(request, problem) + if request.user.has_perm('problems.can_modify_tags'): + return True + if problem is None: + return False + return can_admin_problem(request, problem) def can_admin_instance_of_problem(request, problem): From 4bf9145ea92a65456b89c4f47ba86a00889cef45 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Fri, 27 Dec 2024 12:53:20 +0100 Subject: [PATCH 07/31] fix permissions for contest admins --- oioioi/problems/admin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/oioioi/problems/admin.py b/oioioi/problems/admin.py index 9f293c7e0..b42b51855 100644 --- a/oioioi/problems/admin.py +++ b/oioioi/problems/admin.py @@ -535,18 +535,19 @@ def get_readonly_fields(self, request, obj=None): return self.readonly_fields def change_view(self, request, object_id, form_url='', extra_context=None): + problem = self.get_object(request, unquote(object_id)) extra_context = extra_context or {} extra_context['categories'] = sorted( - set([getattr(inline, 'category', None) for inline in self.get_inlines(request, None)]) + set([getattr(inline, 'category', None) for inline in self.get_inlines(request, problem)]) ) - if request.user.is_superuser or request.user.has_perm('problems.problems_db_admin'): + if can_admin_problem(request, problem): extra_context['no_category'] = NO_CATEGORY return super(ProblemAdmin, self).change_view( request, object_id, form_url, extra_context=extra_context ) def get_inlines(self, request, obj): - if request.user.is_superuser or request.user.has_perm('problems.problems_db_admin'): - return super().get_inlines(request, obj) + self.inlines + if can_admin_problem(request, obj): + return super().get_inlines(request, obj) elif can_modify_tags(request, obj): return self.tag_inlines else: From d5b72efb7ea4425511c48aa290914d2deaf71675 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Fri, 27 Dec 2024 13:26:28 +0100 Subject: [PATCH 08/31] fix permissions for contest admins --- oioioi/problems/admin.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/oioioi/problems/admin.py b/oioioi/problems/admin.py index b42b51855..7750e131a 100644 --- a/oioioi/problems/admin.py +++ b/oioioi/problems/admin.py @@ -302,7 +302,6 @@ def has_delete_permission(self, request, obj=None): form=OriginTagThroughForm, verbose_name=_("origin tag"), verbose_name_plural=_("origin tags"), - has_permission_func=lambda self, request, obj=None: can_modify_tags(request, obj) ) class OriginTagInline(admin.StackedInline): pass @@ -342,7 +341,6 @@ class OriginInfoCategoryAdmin(admin.ModelAdmin): form=OriginInfoValueThroughForm, verbose_name=_("origin information"), verbose_name_plural=_("additional origin information"), - has_permission_func=lambda self, request, obj=None: can_modify_tags(request, obj), ) class OriginInfoValueInline(admin.StackedInline): pass @@ -363,14 +361,6 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): db_field, request, **kwargs ) - def has_add_permission(self, request, obj=None): - return can_modify_tags(request, obj) - - def has_change_permission(self, request, obj=None): - return can_modify_tags(request, obj) - - def has_delete_permission(self, request, obj=None): - return can_modify_tags(request, obj) admin.site.register(OriginInfoValue, OriginInfoValueAdmin) @@ -435,8 +425,6 @@ class ProblemAdmin(admin.ModelAdmin): tag_inlines = ( DifficultyTagInline, AlgorithmTagInline, - OriginTagInline, - OriginInfoValueInline, ) inlines = ( DifficultyTagInline, @@ -540,13 +528,15 @@ def change_view(self, request, object_id, form_url='', extra_context=None): extra_context['categories'] = sorted( set([getattr(inline, 'category', None) for inline in self.get_inlines(request, problem)]) ) - if can_admin_problem(request, problem): + if problem is not None and can_admin_problem(request, problem): + extra_context['no_category'] = NO_CATEGORY + if request.user.has_perm('problems.problems_db_admin'): extra_context['no_category'] = NO_CATEGORY return super(ProblemAdmin, self).change_view( request, object_id, form_url, extra_context=extra_context ) def get_inlines(self, request, obj): - if can_admin_problem(request, obj): + if obj is not None and can_admin_problem(request, obj): return super().get_inlines(request, obj) elif can_modify_tags(request, obj): return self.tag_inlines From a152009d6770797ed14162a1fe800fc7eb2b9307 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Fri, 27 Dec 2024 13:54:06 +0100 Subject: [PATCH 09/31] fix for problems without contests --- oioioi/problems/admin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/oioioi/problems/admin.py b/oioioi/problems/admin.py index 7750e131a..597fff2da 100644 --- a/oioioi/problems/admin.py +++ b/oioioi/problems/admin.py @@ -302,6 +302,7 @@ def has_delete_permission(self, request, obj=None): form=OriginTagThroughForm, verbose_name=_("origin tag"), verbose_name_plural=_("origin tags"), + has_permission_func=lambda self, request, obj=None: request.user.is_superuser, ) class OriginTagInline(admin.StackedInline): pass @@ -341,6 +342,7 @@ class OriginInfoCategoryAdmin(admin.ModelAdmin): form=OriginInfoValueThroughForm, verbose_name=_("origin information"), verbose_name_plural=_("additional origin information"), + has_permission_func=lambda self, request, obj=None: request.user.is_superuser, ) class OriginInfoValueInline(admin.StackedInline): pass @@ -462,6 +464,8 @@ def has_change_permission(self, request, obj=None): return can_modify_tags(request, obj) def has_delete_permission(self, request, obj=None): + if obj is None: + return self.get_queryset(request).exists() return can_admin_problem(request, obj) def redirect_to_list(self, request, problem): From 232f7e5fb7b5869267aacfaa41d34e1f1557fb21 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Mon, 13 Jan 2025 17:41:48 +0100 Subject: [PATCH 10/31] add test --- oioioi/problems/tests/test_tags.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/oioioi/problems/tests/test_tags.py b/oioioi/problems/tests/test_tags.py index 5541112a9..072bb0264 100644 --- a/oioioi/problems/tests/test_tags.py +++ b/oioioi/problems/tests/test_tags.py @@ -2,7 +2,7 @@ import urllib.parse -from django.contrib.auth.models import User +from django.contrib.auth.models import User, Permission from django.test.utils import override_settings from django.urls import reverse from oioioi.base.tests import TestCase @@ -181,7 +181,9 @@ class TestSaveProposals(TestCase): def test_save_proposals_view(self): problem = Problem.objects.get(pk=0) - user = User.objects.get(username='test_admin') + user = User.objects.get(username='test_user') + permission = Permission.objects.get(codename='can_modify_tags') + user.user_permissions.add(permission) self.assertEqual(AlgorithmTagProposal.objects.count(), 0) self.assertEqual(DifficultyTagProposal.objects.count(), 0) @@ -191,7 +193,7 @@ def test_save_proposals_view(self): { 'tags[]': ["Dynamic programming", "Knapsack problem"], 'difficulty': ' \r \t\n Easy \n \t ', - 'user': 'test_admin', + 'user': 'test_user', 'problem': '0', }, ) @@ -220,12 +222,12 @@ def test_save_proposals_view(self): {}, { 'difficulty': 'Easy', - 'user': 'test_admin', + 'user': 'test_user', 'problem': '0', }, { 'tags[]': ["Dynamic programming", "Knapsack problem"], - 'user': 'test_admin', + 'user': 'test_user', 'problem': '0', }, { @@ -236,7 +238,7 @@ def test_save_proposals_view(self): { 'tags[]': ["Dynamic programming", "Knapsack problem"], 'difficulty': 'Easy', - 'user': 'test_admin', + 'user': 'test_user', }, ] From 3a07a62378cbdbc55e97866b1d1ddc3a8cb1eb74 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Mon, 13 Jan 2025 17:57:54 +0100 Subject: [PATCH 11/31] resolve conflicts --- oioioi/problems/tests/test_tags.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/oioioi/problems/tests/test_tags.py b/oioioi/problems/tests/test_tags.py index 072bb0264..5541112a9 100644 --- a/oioioi/problems/tests/test_tags.py +++ b/oioioi/problems/tests/test_tags.py @@ -2,7 +2,7 @@ import urllib.parse -from django.contrib.auth.models import User, Permission +from django.contrib.auth.models import User from django.test.utils import override_settings from django.urls import reverse from oioioi.base.tests import TestCase @@ -181,9 +181,7 @@ class TestSaveProposals(TestCase): def test_save_proposals_view(self): problem = Problem.objects.get(pk=0) - user = User.objects.get(username='test_user') - permission = Permission.objects.get(codename='can_modify_tags') - user.user_permissions.add(permission) + user = User.objects.get(username='test_admin') self.assertEqual(AlgorithmTagProposal.objects.count(), 0) self.assertEqual(DifficultyTagProposal.objects.count(), 0) @@ -193,7 +191,7 @@ def test_save_proposals_view(self): { 'tags[]': ["Dynamic programming", "Knapsack problem"], 'difficulty': ' \r \t\n Easy \n \t ', - 'user': 'test_user', + 'user': 'test_admin', 'problem': '0', }, ) @@ -222,12 +220,12 @@ def test_save_proposals_view(self): {}, { 'difficulty': 'Easy', - 'user': 'test_user', + 'user': 'test_admin', 'problem': '0', }, { 'tags[]': ["Dynamic programming", "Knapsack problem"], - 'user': 'test_user', + 'user': 'test_admin', 'problem': '0', }, { @@ -238,7 +236,7 @@ def test_save_proposals_view(self): { 'tags[]': ["Dynamic programming", "Knapsack problem"], 'difficulty': 'Easy', - 'user': 'test_user', + 'user': 'test_admin', }, ] From 39d5c02bfbcc844acc3818fd815da33928c9d01b Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Mon, 13 Jan 2025 18:18:44 +0100 Subject: [PATCH 12/31] small fix --- oioioi/problems/admin.py | 2 +- .../migrations/0037_merge_20250113_1714.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 oioioi/problems/migrations/0037_merge_20250113_1714.py diff --git a/oioioi/problems/admin.py b/oioioi/problems/admin.py index 7656f3808..3023ef1f7 100644 --- a/oioioi/problems/admin.py +++ b/oioioi/problems/admin.py @@ -508,7 +508,7 @@ def get_queryset(self, request): combined = request.user.problem_set.all() if request.user.is_superuser: return queryset - if request.user.has_perm('problems.problems_db_admin'): + if request.user.has_perm('problems.problems_db_admin') or request.user.has_perm('problems.can_modify_tags'): combined |= queryset.filter(visibility=Problem.VISIBILITY_PUBLIC) if is_contest_basicadmin(request): combined |= queryset.filter(contest=request.contest) diff --git a/oioioi/problems/migrations/0037_merge_20250113_1714.py b/oioioi/problems/migrations/0037_merge_20250113_1714.py new file mode 100644 index 000000000..1f0850643 --- /dev/null +++ b/oioioi/problems/migrations/0037_merge_20250113_1714.py @@ -0,0 +1,14 @@ +# Generated by Django 4.2.16 on 2025-01-13 17:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('problems', '0033_populate_aggregated_tag_proposals'), + ('problems', '0036_alter_problem_options'), + ] + + operations = [ + ] From 15abb1d003ad26e84a27b571e0f1fa4a3a6d9509 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Sun, 19 Jan 2025 22:18:42 +0100 Subject: [PATCH 13/31] test tags tab --- oioioi/problems/tests/test_problem.py | 37 +++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/oioioi/problems/tests/test_problem.py b/oioioi/problems/tests/test_problem.py index 437350e9c..a460d6060 100644 --- a/oioioi/problems/tests/test_problem.py +++ b/oioioi/problems/tests/test_problem.py @@ -429,14 +429,47 @@ def test_settings_tab(self): self.assertContains(response, 'Settings') response = self.client.get(url) self.assertContains(response, 'Add to contest') - self.assertContains(response, 'Current tags') self.assertContains(response, 'Edit problem') self.assertContains(response, 'Edit tests') self.assertContains(response, 'Reupload problem') self.assertContains(response, 'Model solutions') + self.assertContains(response, 'Medium') + + @override_settings(LANGUAGE_CODE='en') + def test_tags_tab_admin(self): + problemsite_url = self._get_site_urls()['statement'] + url = reverse('problem_site', kwargs={'site_key': '123'}) + '?key=tags' + + response = self.client.get(problemsite_url) + self.assertNotContains(response, 'Tags') + + self.assertTrue(self.client.login(username='test_admin')) + response = self.client.get(problemsite_url) + self.assertContains(response, 'Tags') + response = self.client.get(url) + self.assertContains(response, 'Current tags') + self.assertContains(response, 'dp') + self.assertContains(response, 'lcis') + + @override_settings(LANGUAGE_CODE='en') + def test_tags_tab_user_with_permission(self): + problemsite_url = self._get_site_urls()['statement'] + url = reverse('problem_site', kwargs={'site_key': '123'}) + '?key=tags' + + response = self.client.get(problemsite_url) + self.assertNotContains(response, 'Tags') + + user = User.objects.get(username='test_user') + permission = Permission.objects.get(codename='can_modify_tags') + user.user_permissions.add(permission) + + self.assertTrue(self.client.login(username='test_user')) + response = self.client.get(problemsite_url) + self.assertContains(response, 'Tags') + response = self.client.get(url) + self.assertContains(response, 'Current tags') self.assertContains(response, 'dp') self.assertContains(response, 'lcis') - self.assertContains(response, 'Medium') def test_statement_replacement(self): url = ( From 56ca6ccd756caf8e98ad4d95866da7f8bca98e28 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Mon, 20 Jan 2025 18:01:54 +0100 Subject: [PATCH 14/31] squash migrations --- .../migrations/0032_alter_problem_options.py | 17 ----------------- .../migrations/0033_alter_problem_options.py | 17 ----------------- .../migrations/0034_alter_problem_options.py | 6 +++--- .../migrations/0035_alter_problem_options.py | 17 ----------------- .../migrations/0036_alter_problem_options.py | 17 ----------------- .../migrations/0037_merge_20250113_1714.py | 14 -------------- 6 files changed, 3 insertions(+), 85 deletions(-) delete mode 100644 oioioi/problems/migrations/0032_alter_problem_options.py delete mode 100644 oioioi/problems/migrations/0033_alter_problem_options.py delete mode 100644 oioioi/problems/migrations/0035_alter_problem_options.py delete mode 100644 oioioi/problems/migrations/0036_alter_problem_options.py delete mode 100644 oioioi/problems/migrations/0037_merge_20250113_1714.py diff --git a/oioioi/problems/migrations/0032_alter_problem_options.py b/oioioi/problems/migrations/0032_alter_problem_options.py deleted file mode 100644 index 96b7586dc..000000000 --- a/oioioi/problems/migrations/0032_alter_problem_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.16 on 2024-12-04 14:14 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('problems', '0031_auto_20220328_1124'), - ] - - operations = [ - migrations.AlterModelOptions( - name='problem', - options={'permissions': (('tagger', 'Can add and modify tags'), ('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, - ), - ] diff --git a/oioioi/problems/migrations/0033_alter_problem_options.py b/oioioi/problems/migrations/0033_alter_problem_options.py deleted file mode 100644 index 750c775c8..000000000 --- a/oioioi/problems/migrations/0033_alter_problem_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.16 on 2024-12-04 14:37 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('problems', '0032_alter_problem_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='problem', - options={'permissions': (('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, - ), - ] diff --git a/oioioi/problems/migrations/0034_alter_problem_options.py b/oioioi/problems/migrations/0034_alter_problem_options.py index 2727eecce..432e012d0 100644 --- a/oioioi/problems/migrations/0034_alter_problem_options.py +++ b/oioioi/problems/migrations/0034_alter_problem_options.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.16 on 2024-12-04 14:45 +# Generated by Django 4.2.16 on 2025-01-20 16:58 from django.db import migrations @@ -6,12 +6,12 @@ class Migration(migrations.Migration): dependencies = [ - ('problems', '0033_alter_problem_options'), + ('problems', '0033_populate_aggregated_tag_proposals'), ] operations = [ migrations.AlterModelOptions( name='problem', - options={'permissions': (('tagger', 'Can add and modify tags'), ('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, + options={'permissions': (('can_modify_tags', 'Can modify tags'), ('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, ), ] diff --git a/oioioi/problems/migrations/0035_alter_problem_options.py b/oioioi/problems/migrations/0035_alter_problem_options.py deleted file mode 100644 index 169847144..000000000 --- a/oioioi/problems/migrations/0035_alter_problem_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.16 on 2024-12-04 15:09 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('problems', '0034_alter_problem_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='problem', - options={'permissions': (('can_add_tags', 'Can add tags'), ('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, - ), - ] diff --git a/oioioi/problems/migrations/0036_alter_problem_options.py b/oioioi/problems/migrations/0036_alter_problem_options.py deleted file mode 100644 index c4d891947..000000000 --- a/oioioi/problems/migrations/0036_alter_problem_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.16 on 2024-12-24 11:58 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('problems', '0035_alter_problem_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='problem', - options={'permissions': (('can_modify_tags', 'Can modify tags'), ('problems_db_admin', 'Can administer the problems database'), ('problem_admin', 'Can administer the problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'}, - ), - ] diff --git a/oioioi/problems/migrations/0037_merge_20250113_1714.py b/oioioi/problems/migrations/0037_merge_20250113_1714.py deleted file mode 100644 index 1f0850643..000000000 --- a/oioioi/problems/migrations/0037_merge_20250113_1714.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.16 on 2025-01-13 17:14 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('problems', '0033_populate_aggregated_tag_proposals'), - ('problems', '0036_alter_problem_options'), - ] - - operations = [ - ] From f250a64dface868e907044893427720faecf2fc1 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Mon, 20 Jan 2025 18:05:29 +0100 Subject: [PATCH 15/31] remove redundant migrations --- oioioi/base/migrations/0005_permissions.py | 19 ------------------- .../migrations/0006_delete_permissions.py | 16 ---------------- 2 files changed, 35 deletions(-) delete mode 100644 oioioi/base/migrations/0005_permissions.py delete mode 100644 oioioi/base/migrations/0006_delete_permissions.py diff --git a/oioioi/base/migrations/0005_permissions.py b/oioioi/base/migrations/0005_permissions.py deleted file mode 100644 index f6cfa1759..000000000 --- a/oioioi/base/migrations/0005_permissions.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.2.16 on 2024-12-04 14:37 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('base', '0004_alter_userpreferences_enable_editor'), - ] - - operations = [ - migrations.CreateModel( - name='Permissions', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ], - ), - ] diff --git a/oioioi/base/migrations/0006_delete_permissions.py b/oioioi/base/migrations/0006_delete_permissions.py deleted file mode 100644 index 4bbf3b9c1..000000000 --- a/oioioi/base/migrations/0006_delete_permissions.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 4.2.16 on 2024-12-04 14:45 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('base', '0005_permissions'), - ] - - operations = [ - migrations.DeleteModel( - name='Permissions', - ), - ] From 8df28ed38f78a33e9b5bf833a6e38f1832814cb6 Mon Sep 17 00:00:00 2001 From: atanazy Date: Wed, 26 Feb 2025 17:34:50 +0100 Subject: [PATCH 16/31] display nickname next to the full name when displaying contest pupils --- oioioi/teachers/templates/teachers/members.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oioioi/teachers/templates/teachers/members.html b/oioioi/teachers/templates/teachers/members.html index 9d6468e4b..296ea19fb 100644 --- a/oioioi/teachers/templates/teachers/members.html +++ b/oioioi/teachers/templates/teachers/members.html @@ -218,7 +218,7 @@

{% trans "Members" %}

From f612e8bfac9c5905c13964e69add4cf91fc741ba Mon Sep 17 00:00:00 2001 From: atanazy Date: Wed, 5 Mar 2025 16:19:50 +0100 Subject: [PATCH 17/31] add logic and use member.username --- oioioi/teachers/templates/teachers/members.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/oioioi/teachers/templates/teachers/members.html b/oioioi/teachers/templates/teachers/members.html index 296ea19fb..e782b2376 100644 --- a/oioioi/teachers/templates/teachers/members.html +++ b/oioioi/teachers/templates/teachers/members.html @@ -218,7 +218,11 @@

{% trans "Members" %}

From 27e257c36849a006ed38d68ab366fe06db4fdff5 Mon Sep 17 00:00:00 2001 From: atanazy Date: Wed, 5 Mar 2025 18:27:03 +0100 Subject: [PATCH 18/31] fix more similar cases --- oioioi/teachers/templates/teachers/members.html | 16 +++++++++------- oioioi/teachers/utils.py | 2 +- .../templates/usergroups/confirm_detaching.html | 2 +- .../usergroups/teacher_usergroup_detail.html | 4 ++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/oioioi/teachers/templates/teachers/members.html b/oioioi/teachers/templates/teachers/members.html index e782b2376..00771baac 100644 --- a/oioioi/teachers/templates/teachers/members.html +++ b/oioioi/teachers/templates/teachers/members.html @@ -86,11 +86,17 @@
- {% call_method_with_arguments usergroup.members 'values' 'first_name' 'last_name' as group_members %} + {% call_method_with_arguments usergroup.members 'values' 'first_name' 'last_name' 'username' as group_members %} {% if group_members %}
    {% for group_member in group_members|slice:":23" %} -
  • {{ group_member.first_name }} {{ group_member.last_name }}
  • +
  • + {% if group_member.first_name or group_member.last_name %} + {{ group_member.last_name|add:" " |add:group_member.first_name }} + {% else %} + {{ group_member.username }} + {% endif %} +
  • {% endfor %} {% if group_members.count > 23 %} @@ -218,11 +224,7 @@

    {% trans "Members" %}

diff --git a/oioioi/teachers/utils.py b/oioioi/teachers/utils.py index a39ebc08e..f0220e96b 100644 --- a/oioioi/teachers/utils.py +++ b/oioioi/teachers/utils.py @@ -62,4 +62,4 @@ def add_user_to_contest_as(user, contest, member_type): teacher = get_user_teacher_obj(user) ContestTeacher.objects.get_or_create( contest=contest, teacher=teacher - ) + ) \ No newline at end of file diff --git a/oioioi/usergroups/templates/usergroups/confirm_detaching.html b/oioioi/usergroups/templates/usergroups/confirm_detaching.html index f26dde4a2..e189e0407 100644 --- a/oioioi/usergroups/templates/usergroups/confirm_detaching.html +++ b/oioioi/usergroups/templates/usergroups/confirm_detaching.html @@ -19,7 +19,7 @@

{% trans "The following users will lose access to the contest:" %}

    {% for user in removed_users %} -
  • {{ user.get_full_name }}
  • +
  • {{ user.get_full_name|default:user.username }}
  • {% endfor %}
{% endif %} diff --git a/oioioi/usergroups/templates/usergroups/teacher_usergroup_detail.html b/oioioi/usergroups/templates/usergroups/teacher_usergroup_detail.html index 6ba3a2f9d..39c292543 100644 --- a/oioioi/usergroups/templates/usergroups/teacher_usergroup_detail.html +++ b/oioioi/usergroups/templates/usergroups/teacher_usergroup_detail.html @@ -68,7 +68,7 @@

{% trans "Members" %}

@@ -104,7 +104,7 @@

{% trans "Owners" %}

From bf63ecfd898a19fde8f06a189a4f2a3bcc21d702 Mon Sep 17 00:00:00 2001 From: atanazy Date: Thu, 6 Mar 2025 08:59:39 +0100 Subject: [PATCH 19/31] fix order of names --- oioioi/teachers/templates/teachers/members.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oioioi/teachers/templates/teachers/members.html b/oioioi/teachers/templates/teachers/members.html index 00771baac..4bd805f6f 100644 --- a/oioioi/teachers/templates/teachers/members.html +++ b/oioioi/teachers/templates/teachers/members.html @@ -92,7 +92,7 @@
{% for group_member in group_members|slice:":23" %}
  • {% if group_member.first_name or group_member.last_name %} - {{ group_member.last_name|add:" " |add:group_member.first_name }} + {{ group_member.first_name|add:" " |add:group_member.last_name }} {% else %} {{ group_member.username }} {% endif %} From 26e48c5690f7143a04a224b441d9d78153401c93 Mon Sep 17 00:00:00 2001 From: atanazy Date: Wed, 12 Mar 2025 16:13:06 +0100 Subject: [PATCH 20/31] more elegant code --- oioioi/teachers/templates/teachers/members.html | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/oioioi/teachers/templates/teachers/members.html b/oioioi/teachers/templates/teachers/members.html index 4bd805f6f..b2d5b5cc4 100644 --- a/oioioi/teachers/templates/teachers/members.html +++ b/oioioi/teachers/templates/teachers/members.html @@ -86,16 +86,12 @@
    - {% call_method_with_arguments usergroup.members 'values' 'first_name' 'last_name' 'username' as group_members %} + {% call_method_with_arguments usergroup.members 'values' 'username' as group_members %} {% if group_members %}
      {% for group_member in group_members|slice:":23" %}
    • - {% if group_member.first_name or group_member.last_name %} - {{ group_member.first_name|add:" " |add:group_member.last_name }} - {% else %} - {{ group_member.username }} - {% endif %} + {{ group_member.get_full_name|default:group_member.username }}
    • {% endfor %} From 0d13af7e55c029953469a0942e8c0c02f42e65d2 Mon Sep 17 00:00:00 2001 From: atanazy Date: Wed, 12 Mar 2025 16:22:17 +0100 Subject: [PATCH 21/31] more elegant code --- oioioi/teachers/templates/teachers/members.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/oioioi/teachers/templates/teachers/members.html b/oioioi/teachers/templates/teachers/members.html index b2d5b5cc4..cfed1bcb5 100644 --- a/oioioi/teachers/templates/teachers/members.html +++ b/oioioi/teachers/templates/teachers/members.html @@ -90,9 +90,7 @@
      {% if group_members %}
        {% for group_member in group_members|slice:":23" %} -
      • - {{ group_member.get_full_name|default:group_member.username }} -
      • +
      • {{ group_member.get_full_name|default:group_member.username }}
      • {% endfor %} {% if group_members.count > 23 %} From 776f61cbb7e8c3216182dc7325b60f67719ce2d4 Mon Sep 17 00:00:00 2001 From: atanazy Date: Wed, 12 Mar 2025 16:32:07 +0100 Subject: [PATCH 22/31] revert changes --- oioioi/teachers/templates/teachers/members.html | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/oioioi/teachers/templates/teachers/members.html b/oioioi/teachers/templates/teachers/members.html index cfed1bcb5..9c5ca359e 100644 --- a/oioioi/teachers/templates/teachers/members.html +++ b/oioioi/teachers/templates/teachers/members.html @@ -86,11 +86,17 @@
    - {% call_method_with_arguments usergroup.members 'values' 'username' as group_members %} + {% call_method_with_arguments usergroup.members 'values' 'first_name' 'last_name' 'username' as group_members %} {% if group_members %}
      {% for group_member in group_members|slice:":23" %} -
    • {{ group_member.get_full_name|default:group_member.username }}
    • +
    • + {% if group_member.first_name or group_member.last_name %} + {{ group_member.first_name|add:" " |add:group_member.last_name }} + {% else %} + {{ group_member.username }} + {% endif %} +
    • {% endfor %} {% if group_members.count > 23 %} @@ -264,4 +270,4 @@

      {% trans "Members" %}

    -{% endblock %} +{% endblock %} \ No newline at end of file From a0f86fbbd78ce55ed4cee8905f434e5d03a24eca Mon Sep 17 00:00:00 2001 From: atanazy Date: Wed, 12 Mar 2025 17:35:23 +0100 Subject: [PATCH 23/31] more elegant code --- oioioi/teachers/templates/teachers/members.html | 13 +++---------- oioioi/teachers/utils.py | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/oioioi/teachers/templates/teachers/members.html b/oioioi/teachers/templates/teachers/members.html index 9c5ca359e..849d1d32e 100644 --- a/oioioi/teachers/templates/teachers/members.html +++ b/oioioi/teachers/templates/teachers/members.html @@ -86,17 +86,10 @@
    - {% call_method_with_arguments usergroup.members 'values' 'first_name' 'last_name' 'username' as group_members %} - {% if group_members %} + {% if usergroup.members.count %}
      - {% for group_member in group_members|slice:":23" %} -
    • - {% if group_member.first_name or group_member.last_name %} - {{ group_member.first_name|add:" " |add:group_member.last_name }} - {% else %} - {{ group_member.username }} - {% endif %} -
    • + {% for member in usergroup.members.all|slice:":23" %} +
    • {{ member.get_full_name|default:member.username }}
    • {% endfor %} {% if group_members.count > 23 %} diff --git a/oioioi/teachers/utils.py b/oioioi/teachers/utils.py index f0220e96b..a39ebc08e 100644 --- a/oioioi/teachers/utils.py +++ b/oioioi/teachers/utils.py @@ -62,4 +62,4 @@ def add_user_to_contest_as(user, contest, member_type): teacher = get_user_teacher_obj(user) ContestTeacher.objects.get_or_create( contest=contest, teacher=teacher - ) \ No newline at end of file + ) From 34edc911c14f0caf3d407bc6f9005f41ca23fd60 Mon Sep 17 00:00:00 2001 From: atanazy Date: Tue, 25 Mar 2025 20:02:41 +0100 Subject: [PATCH 24/31] add buttons to expand group member lists --- .../usergroups/teacher_usergroups_list.css | 52 +++++++++++++++++++ .../usergroups/teacher_usergroups_list.html | 37 +++++++++++-- 2 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 oioioi/usergroups/static/usergroups/teacher_usergroups_list.css diff --git a/oioioi/usergroups/static/usergroups/teacher_usergroups_list.css b/oioioi/usergroups/static/usergroups/teacher_usergroups_list.css new file mode 100644 index 000000000..8a56c3b35 --- /dev/null +++ b/oioioi/usergroups/static/usergroups/teacher_usergroups_list.css @@ -0,0 +1,52 @@ +.usergroup { + padding: 0.3em 1rem; +} + +.usergroup button { + white-space: normal; +} + +.usergroup-title:focus { + outline: none; + box-shadow: none; +} + +.usergroup-user-list { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-content: space-evenly; + padding-left: 1%; +} + +.usergroup-user-list li { + width: 20%; + margin: 0.5em; + overflow-wrap: break-word; +} + +.usergroup-title { + white-space: normal; + width: 100%; + height: 100%; + text-align: left; +} + +.usergroup-body { + padding-top: 0.25em; +} + +.usergroup-title:focus:hover, +.usergroup-title:focus, +.usergroup-title:active { + outline: 0; + border: 0; +} + +.usergroup-title:hover { + text-decoration: none; +} + +.group-button { + margin: 0.5em 0.25em; +} diff --git a/oioioi/usergroups/templates/usergroups/teacher_usergroups_list.html b/oioioi/usergroups/templates/usergroups/teacher_usergroups_list.html index 7fe6f4f58..09175a779 100644 --- a/oioioi/usergroups/templates/usergroups/teacher_usergroups_list.html +++ b/oioioi/usergroups/templates/usergroups/teacher_usergroups_list.html @@ -5,6 +5,11 @@ {% trans "Your User Groups" %} {% endblock %} +{% block styles %} +{{ block.super }} + +{% endblock %} + {% block main-content %}

      {% trans "Your Groups" %} @@ -23,8 +28,34 @@

      {% paginate %}
        - {% for group in usergroup_list %} -
      • {{ group }}
      • + {% for usergroup in usergroup_list %} +
      • +
        + +
        +
        +
        + {% if usergroup.members.count %} +
          + {% for member in usergroup.members.all|slice:":23" %} +
        • {{ member.get_full_name|default:member.username }}
        • + {% endfor %} + + {% if group_members.count > 23 %} +
        • + {% endif %} +
        + {% else %} +

        {% trans "Strange, this group doesn't have any members." %}

        + {% endif %} +
        + +
        +
      • {% endfor%}
      {% paginate %} @@ -34,4 +65,4 @@

      -{% endblock %} +{% endblock %} \ No newline at end of file From ae9d7d251ed5462226aa3c16f033ff4562cc3210 Mon Sep 17 00:00:00 2001 From: atanazy Date: Wed, 2 Apr 2025 16:23:31 +0200 Subject: [PATCH 25/31] fix minor bugs --- oioioi/teachers/templates/teachers/members.html | 2 +- oioioi/usergroups/templates/usergroups/confirm_detaching.html | 2 +- .../templates/usergroups/teacher_usergroups_list.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/oioioi/teachers/templates/teachers/members.html b/oioioi/teachers/templates/teachers/members.html index 849d1d32e..aaaf358df 100644 --- a/oioioi/teachers/templates/teachers/members.html +++ b/oioioi/teachers/templates/teachers/members.html @@ -92,7 +92,7 @@
    • {{ member.get_full_name|default:member.username }}
    • {% endfor %} - {% if group_members.count > 23 %} + {% if usergroup.members.count > 23 %}
    • {% endif %}

    diff --git a/oioioi/usergroups/templates/usergroups/confirm_detaching.html b/oioioi/usergroups/templates/usergroups/confirm_detaching.html index e189e0407..812844550 100644 --- a/oioioi/usergroups/templates/usergroups/confirm_detaching.html +++ b/oioioi/usergroups/templates/usergroups/confirm_detaching.html @@ -8,7 +8,7 @@ {% block styles %} {{ block.super }} -{% endblock %}} +{% endblock %} {% block help_text %} {% blocktrans %} diff --git a/oioioi/usergroups/templates/usergroups/teacher_usergroups_list.html b/oioioi/usergroups/templates/usergroups/teacher_usergroups_list.html index 09175a779..c338ef610 100644 --- a/oioioi/usergroups/templates/usergroups/teacher_usergroups_list.html +++ b/oioioi/usergroups/templates/usergroups/teacher_usergroups_list.html @@ -43,7 +43,7 @@

  • {{ member.get_full_name|default:member.username }}
  • {% endfor %} - {% if group_members.count > 23 %} + {% if usergroup.members.count > 23 %}
  • {% endif %} From 9590f5cd5b0036add7cf040ca040434b5e883f99 Mon Sep 17 00:00:00 2001 From: atanazy Date: Wed, 2 Apr 2025 18:44:11 +0200 Subject: [PATCH 26/31] add tests --- .../fixtures/test_big_usergroup.json | 489 ++++++++++++++++++ .../usergroups/fixtures/test_usergroups.json | 21 +- oioioi/usergroups/tests.py | 39 +- 3 files changed, 535 insertions(+), 14 deletions(-) create mode 100644 oioioi/usergroups/fixtures/test_big_usergroup.json diff --git a/oioioi/usergroups/fixtures/test_big_usergroup.json b/oioioi/usergroups/fixtures/test_big_usergroup.json new file mode 100644 index 000000000..dfa7f3016 --- /dev/null +++ b/oioioi/usergroups/fixtures/test_big_usergroup.json @@ -0,0 +1,489 @@ +[ + { + "pk": 3001, + "model": "usergroups.UserGroup", + "fields": { + "name": "big group", + "owners": [ + 1001 + ], + "members": [ + 3001, + 3002, + 3003, + 3004, + 3005, + 3006, + 3007, + 3008, + 3009, + 3010, + 3011, + 3012, + 3013, + 3014, + 3015, + 3016, + 3017, + 3018, + 3019, + 3020, + 3021, + 3022, + 3023, + 3024, + 3025 + ] + } + }, + { + "pk": 3001, + "model": "auth.user", + "fields": { + "username": "user1", + "first_name": "First1", + "last_name": "Last1", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user1@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3002, + "model": "auth.user", + "fields": { + "username": "user2", + "first_name": "First2", + "last_name": "Last2", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user2@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3003, + "model": "auth.user", + "fields": { + "username": "user3", + "first_name": "First3", + "last_name": "Last3", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user3@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3004, + "model": "auth.user", + "fields": { + "username": "user4", + "first_name": "First4", + "last_name": "Last4", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user4@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3005, + "model": "auth.user", + "fields": { + "username": "user5", + "first_name": "First5", + "last_name": "Last5", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user5@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3006, + "model": "auth.user", + "fields": { + "username": "user6", + "first_name": "First6", + "last_name": "Last6", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user6@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3007, + "model": "auth.user", + "fields": { + "username": "user7", + "first_name": "First7", + "last_name": "Last7", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user7@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3008, + "model": "auth.user", + "fields": { + "username": "user8", + "first_name": "First8", + "last_name": "Last8", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user8@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3009, + "model": "auth.user", + "fields": { + "username": "user9", + "first_name": "First9", + "last_name": "Last9", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user9@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3010, + "model": "auth.user", + "fields": { + "username": "user10", + "first_name": "First10", + "last_name": "Last10", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user10@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3011, + "model": "auth.user", + "fields": { + "username": "user11", + "first_name": "First11", + "last_name": "Last11", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user11@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3012, + "model": "auth.user", + "fields": { + "username": "user12", + "first_name": "First12", + "last_name": "Last12", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user12@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3013, + "model": "auth.user", + "fields": { + "username": "user13", + "first_name": "First13", + "last_name": "Last13", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user13@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3014, + "model": "auth.user", + "fields": { + "username": "user14", + "first_name": "First14", + "last_name": "Last14", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user14@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3015, + "model": "auth.user", + "fields": { + "username": "user15", + "first_name": "First15", + "last_name": "Last15", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user15@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3016, + "model": "auth.user", + "fields": { + "username": "user16", + "first_name": "First16", + "last_name": "Last16", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user16@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3017, + "model": "auth.user", + "fields": { + "username": "user17", + "first_name": "First17", + "last_name": "Last17", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user17@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3018, + "model": "auth.user", + "fields": { + "username": "user18", + "first_name": "First18", + "last_name": "Last18", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user18@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3019, + "model": "auth.user", + "fields": { + "username": "user19", + "first_name": "First19", + "last_name": "Last19", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user19@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3020, + "model": "auth.user", + "fields": { + "username": "user20", + "first_name": "First20", + "last_name": "Last20", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user20@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3021, + "model": "auth.user", + "fields": { + "username": "user21", + "first_name": "First21", + "last_name": "Last21", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user21@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3022, + "model": "auth.user", + "fields": { + "username": "user22", + "first_name": "First22", + "last_name": "Last22", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user22@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3023, + "model": "auth.user", + "fields": { + "username": "user23", + "first_name": "First23", + "last_name": "Last23", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user23@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3024, + "model": "auth.user", + "fields": { + "username": "user24", + "first_name": "First24", + "last_name": "Last24", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user24@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + }, + { + "pk": 3025, + "model": "auth.user", + "fields": { + "username": "user25", + "first_name": "First25", + "last_name": "Last25", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "user25@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } + } +] \ No newline at end of file diff --git a/oioioi/usergroups/fixtures/test_usergroups.json b/oioioi/usergroups/fixtures/test_usergroups.json index 857ca88ea..72c6cf4b6 100644 --- a/oioioi/usergroups/fixtures/test_usergroups.json +++ b/oioioi/usergroups/fixtures/test_usergroups.json @@ -63,7 +63,8 @@ ], "members": [ 1001, - 1003 + 1003, + 2006 ], "addition_config": 1002, "sharing_config": 1003 @@ -103,5 +104,23 @@ "addition_config": 1001, "sharing_config": 1004 } + }, + { + "pk": 2006, + "model": "auth.user", + "fields": { + "username": "no_name", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2012-07-31T20:27:58.768Z", + "groups": [], + "user_permissions": [], + "password": "", + "email": "test_admin2@example.com", + "date_joined": "2012-07-31T20:27:58.768Z" + } } ] \ No newline at end of file diff --git a/oioioi/usergroups/tests.py b/oioioi/usergroups/tests.py index 8300e9f1d..9d6362231 100644 --- a/oioioi/usergroups/tests.py +++ b/oioioi/usergroups/tests.py @@ -120,9 +120,15 @@ def test_usergroup_validation(self): class TestTeachersViews(TestCase): - fixtures = ['test_users', 'teachers', 'test_action_configs', 'test_usergroups'] + fixtures = ['test_users', 'teachers', 'test_action_configs', 'test_usergroups', 'test_big_usergroup'] def test_visibility(self): + self.assertTrue(self.client.login(username='test_admin')) + url = reverse('teacher_usergroups_list') + response = self.client.get(url) + self.assertContains(response, 'group 1') + self.assertNotContains(response, '…') + self.assertTrue(self.client.login(username='test_user')) # teacher url = reverse('teacher_usergroups_list') @@ -130,6 +136,13 @@ def test_visibility(self): self.assertContains(response, 'User Groups', count=3) # sidebar self.assertContains(response, 'Your Groups') self.assertContains(response, 'teacher group') + self.assertContains(response, 'Test User') + self.assertContains(response, 'no_name') # user without first and last name + self.assertContains(response, 'big group') + self.assertContains(response, 'First23 Last23') + self.assertNotContains(response, 'First24 Last24') + self.assertContains(response, '…') # big group has 25 members + self.assertContains(response, 'Modify group') self.assertNotContains(response, 'group 1') url = reverse('teacher_usergroups_add_group') @@ -206,12 +219,12 @@ def test_addition(self): self.assertEqual(response.status_code, 200) data = {'name': 'new group'} - self.assertEqual(UserGroup.objects.count(), 5) - self.assertEqual(ActionConfig.objects.count(), 9) - response = self.client.post(url, data) - self.assertEqual(response.status_code, 302) self.assertEqual(UserGroup.objects.count(), 6) self.assertEqual(ActionConfig.objects.count(), 11) + response = self.client.post(url, data) + self.assertEqual(response.status_code, 302) + self.assertEqual(UserGroup.objects.count(), 7) + self.assertEqual(ActionConfig.objects.count(), 13) self.assertTrue(UserGroup.objects.filter(name='new group').exists()) group = UserGroup.objects.filter(name='new group').first() @@ -227,23 +240,23 @@ def test_deletion(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(UserGroup.objects.count(), 5) - self.assertEqual(ActionConfig.objects.count(), 9) + self.assertEqual(UserGroup.objects.count(), 6) + self.assertEqual(ActionConfig.objects.count(), 11) response = self.client.post(url) self.assertEqual(response.status_code, 302) - self.assertEqual(UserGroup.objects.count(), 4) - self.assertEqual(ActionConfig.objects.count(), 7) + self.assertEqual(UserGroup.objects.count(), 5) + self.assertEqual(ActionConfig.objects.count(), 9) url = reverse('delete_usergroup_confirmation', kwargs={'usergroup_id': 1004}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(UserGroup.objects.count(), 4) - self.assertEqual(ActionConfig.objects.count(), 7) + self.assertEqual(UserGroup.objects.count(), 5) + self.assertEqual(ActionConfig.objects.count(), 9) response = self.client.post(url) self.assertEqual(response.status_code, 302) - self.assertEqual(UserGroup.objects.count(), 3) - self.assertEqual(ActionConfig.objects.count(), 6) + self.assertEqual(UserGroup.objects.count(), 4) + self.assertEqual(ActionConfig.objects.count(), 8) def test_delete_members(self): self.assertTrue(self.client.login(username='test_user')) # teacher From 1c1e57362e9bfb2c4201e5ed12cb1524b879e412 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Wed, 16 Apr 2025 17:17:14 +0200 Subject: [PATCH 27/31] add test option to check if variables in template tags are defined --- conftest.py | 8 +++++++- oioioi/base/templatetags/strict_variable.py | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 oioioi/base/templatetags/strict_variable.py diff --git a/conftest.py b/conftest.py index e5e6ef2c6..429335245 100644 --- a/conftest.py +++ b/conftest.py @@ -4,19 +4,25 @@ from oioioi.base.tests import pytest_plugin as base_plugin from oioioi.contests.tests import pytest_plugin as contests_plugin +from django.template.base import Variable +from oioioi.base.templatetags.strict_variable import StrictVariable def pytest_addoption(parser): parser.addoption( '--runslow', action='store_true', default=False, help="run slow tests" ) - + parser.addoption("--strict-template-vars", action="store_true", help="Raise errors for undefined template variables") # called for running each test def pytest_runtest_setup(item): contests_plugin.pytest_runtest_setup(item) base_plugin.pytest_runtest_setup(item) +def pytest_configure(config): + if config.getoption("--strict-template-vars"): + Variable.resolve = StrictVariable.resolve + def pytest_collection_modifyitems(config, items): # --runslow flag: do not skip slow tests diff --git a/oioioi/base/templatetags/strict_variable.py b/oioioi/base/templatetags/strict_variable.py new file mode 100644 index 000000000..c64168007 --- /dev/null +++ b/oioioi/base/templatetags/strict_variable.py @@ -0,0 +1,9 @@ +from django.template.base import Variable +from django.template import TemplateSyntaxError + +class StrictVariable(Variable): + def resolve(self, context): + try: + return super().resolve(context) + except KeyError: + raise TemplateSyntaxError(f"Undefined variable: '{self.var}'") From 4238d62ff136980b520dd0ab0d96c9957d76505e Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Wed, 16 Apr 2025 17:18:45 +0200 Subject: [PATCH 28/31] code style --- conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conftest.py b/conftest.py index 429335245..3bd265601 100644 --- a/conftest.py +++ b/conftest.py @@ -12,7 +12,8 @@ def pytest_addoption(parser): parser.addoption( '--runslow', action='store_true', default=False, help="run slow tests" ) - parser.addoption("--strict-template-vars", action="store_true", help="Raise errors for undefined template variables") + parser.addoption( + '--strict-template-vars', action='store_true', help="Raise errors for undefined template variables") # called for running each test def pytest_runtest_setup(item): From 9dcbcfdc02dc75c6658b0bcf2d8d926a86d9d087 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Wed, 16 Apr 2025 17:47:11 +0200 Subject: [PATCH 29/31] better approach --- conftest.py | 6 ++---- oioioi/base/templatetags/strict_variable.py | 9 --------- 2 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 oioioi/base/templatetags/strict_variable.py diff --git a/conftest.py b/conftest.py index 3bd265601..f4ad9f4a7 100644 --- a/conftest.py +++ b/conftest.py @@ -4,9 +4,7 @@ from oioioi.base.tests import pytest_plugin as base_plugin from oioioi.contests.tests import pytest_plugin as contests_plugin -from django.template.base import Variable -from oioioi.base.templatetags.strict_variable import StrictVariable - +from django.conf import settings def pytest_addoption(parser): parser.addoption( @@ -22,7 +20,7 @@ def pytest_runtest_setup(item): def pytest_configure(config): if config.getoption("--strict-template-vars"): - Variable.resolve = StrictVariable.resolve + settings.TEMPLATES[0]['OPTIONS']['string_if_invalid'] = '{% templatetag openvariable %} INVALID_VAR: %s {% templatetag closevariable %}' def pytest_collection_modifyitems(config, items): diff --git a/oioioi/base/templatetags/strict_variable.py b/oioioi/base/templatetags/strict_variable.py deleted file mode 100644 index c64168007..000000000 --- a/oioioi/base/templatetags/strict_variable.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.template.base import Variable -from django.template import TemplateSyntaxError - -class StrictVariable(Variable): - def resolve(self, context): - try: - return super().resolve(context) - except KeyError: - raise TemplateSyntaxError(f"Undefined variable: '{self.var}'") From 67f2dc7f5cf58a500fab98776843103e373a9701 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Wed, 16 Apr 2025 17:49:32 +0200 Subject: [PATCH 30/31] better approach --- conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conftest.py b/conftest.py index f4ad9f4a7..5be6b9136 100644 --- a/conftest.py +++ b/conftest.py @@ -6,6 +6,7 @@ from oioioi.contests.tests import pytest_plugin as contests_plugin from django.conf import settings + def pytest_addoption(parser): parser.addoption( '--runslow', action='store_true', default=False, help="run slow tests" @@ -20,6 +21,7 @@ def pytest_runtest_setup(item): def pytest_configure(config): if config.getoption("--strict-template-vars"): + # this will raise an error if a template variable is not defined settings.TEMPLATES[0]['OPTIONS']['string_if_invalid'] = '{% templatetag openvariable %} INVALID_VAR: %s {% templatetag closevariable %}' From 3afd0c053691c53a5acc74500bddd0186106ae61 Mon Sep 17 00:00:00 2001 From: Atanazy Gawrysiak Date: Wed, 16 Apr 2025 17:50:10 +0200 Subject: [PATCH 31/31] cleaner code --- conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conftest.py b/conftest.py index 5be6b9136..a01f8bccc 100644 --- a/conftest.py +++ b/conftest.py @@ -14,11 +14,13 @@ def pytest_addoption(parser): parser.addoption( '--strict-template-vars', action='store_true', help="Raise errors for undefined template variables") + # called for running each test def pytest_runtest_setup(item): contests_plugin.pytest_runtest_setup(item) base_plugin.pytest_runtest_setup(item) + def pytest_configure(config): if config.getoption("--strict-template-vars"): # this will raise an error if a template variable is not defined