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

Add permission to change tags #445

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
11 changes: 9 additions & 2 deletions oioioi/base/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions oioioi/base/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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_modify_tags')


class OioioiPasswordResetForm(PasswordResetForm):
Expand Down
19 changes: 19 additions & 0 deletions oioioi/base/migrations/0005_permissions.py
Original file line number Diff line number Diff line change
@@ -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')),
],
),
]
16 changes: 16 additions & 0 deletions oioioi/base/migrations/0006_delete_permissions.py
Original file line number Diff line number Diff line change
@@ -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',
),
]
52 changes: 45 additions & 7 deletions oioioi/problems/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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 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)


class BaseTagAdmin(admin.ModelAdmin):
filter_horizontal = ('problems',)

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)


@tag_inline(
model=OriginTag.problems.through,
Expand Down Expand Up @@ -344,7 +362,7 @@ def formfield_for_manytomany(self, db_field, request, **kwargs):
return super(OriginInfoValueAdmin, self).formfield_for_manytomany(
db_field, request, **kwargs
)


admin.site.register(OriginInfoValue, OriginInfoValueAdmin)

Expand All @@ -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: can_modify_tags(request, obj),
)
class DifficultyTagInline(admin.StackedInline):
pass
Expand Down Expand Up @@ -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: can_modify_tags(request, obj),
)
class AlgorithmTagInline(admin.StackedInline):
pass
Expand All @@ -404,6 +424,10 @@ def formfield_for_manytomany(self, db_field, request, **kwargs):


class ProblemAdmin(admin.ModelAdmin):
tag_inlines = (
DifficultyTagInline,
AlgorithmTagInline,
)
inlines = (
DifficultyTagInline,
AlgorithmTagInline,
Expand Down Expand Up @@ -437,10 +461,12 @@ 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_modify_tags(request, obj)

def has_delete_permission(self, request, obj=None):
return self.has_change_permission(request, obj)
if obj is None:
return self.get_queryset(request).exists()
return can_admin_problem(request, obj)

def redirect_to_list(self, request, problem):
if problem.contest:
Expand Down Expand Up @@ -482,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)
Expand All @@ -503,14 +529,26 @@ 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.inlines])
set([getattr(inline, 'category', None) for inline in self.get_inlines(request, problem)])
)
extra_context['no_category'] = NO_CATEGORY
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 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
else:
return ()



class BaseProblemAdmin(admin.MixinsAdmin):
Expand Down
17 changes: 17 additions & 0 deletions oioioi/problems/migrations/0032_alter_problem_options.py
Original file line number Diff line number Diff line change
@@ -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'},
),
]
17 changes: 17 additions & 0 deletions oioioi/problems/migrations/0033_alter_problem_options.py
Original file line number Diff line number Diff line change
@@ -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'},
),
]
17 changes: 17 additions & 0 deletions oioioi/problems/migrations/0034_alter_problem_options.py
Original file line number Diff line number Diff line change
@@ -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'},
),
]
17 changes: 17 additions & 0 deletions oioioi/problems/migrations/0035_alter_problem_options.py
Original file line number Diff line number Diff line change
@@ -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'},
),
]
17 changes: 17 additions & 0 deletions oioioi/problems/migrations/0036_alter_problem_options.py
Original file line number Diff line number Diff line change
@@ -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'},
),
]
14 changes: 14 additions & 0 deletions oioioi/problems/migrations/0037_merge_20250113_1714.py
Original file line number Diff line number Diff line change
@@ -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 = [
]
1 change: 1 addition & 0 deletions oioioi/problems/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class Meta(object):
verbose_name = _("problem")
verbose_name_plural = _("problems")
permissions = (
('can_modify_tags', _("Can modify tags")),
('problems_db_admin', _("Can administer the problems database")),
('problem_admin', _("Can administer the problem")),
)
Expand Down
24 changes: 19 additions & 5 deletions oioioi/problems/problem_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
)
from oioioi.problems.problem_sources import UploadedPackageSource
from oioioi.problems.utils import (
can_modify_tags,
can_admin_problem,
generate_add_to_contest_metadata,
generate_model_solutions_context,
Expand Down Expand Up @@ -249,6 +250,23 @@ 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)

return TemplateResponse(
request,
'problems/settings.html',
{
'site_key': problem.problemsite.url_key,
'problem': problem,
'administered_recent_contests': administered_recent_contests,
'package': package if package and package.package_file else None,
'model_solutions': model_solutions,
'can_admin_problem': can_admin_problem(request, problem),
'extra_actions': extra_actions,
},
)

@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]
)
Expand All @@ -258,17 +276,13 @@ def problem_site_settings(request, problem):

return TemplateResponse(
request,
'problems/settings.html',
'problems/tags.html',
{
'site_key': problem.problemsite.url_key,
'problem': 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,
},
)

Expand Down
Loading
Loading