Skip to content

Commit 535626b

Browse files
Multiple additional admin operations for Problem model (#511)
* Add assigning to a round * Improve appearance and handle edge cases * Add tests for assigning problems to rounds * Add safer handling of problem_ids * Add better test coverage * Fix test_bad_problem_ids * Add tests for managing problems from another contest * Handle reattaching problems safer * Fix small issue with tests * Include twalen's suggestions
1 parent 5054235 commit 535626b

File tree

8 files changed

+685
-13
lines changed

8 files changed

+685
-13
lines changed

oioioi/contests/admin.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ class ProblemInstanceAdmin(admin.ModelAdmin):
382382
list_display = ('name_link', 'short_name_link', 'round', 'package', 'actions_field')
383383
readonly_fields = ('contest', 'problem')
384384
ordering = ('-round__start_date', 'short_name')
385-
actions = ['attach_problems_to_another_contest']
385+
actions = ['attach_problems_to_another_contest', 'assign_problems_to_a_round']
386386

387387
def attach_problems_to_another_contest(self, request, queryset):
388388
ids = [problem.id for problem in queryset]
@@ -393,6 +393,15 @@ def attach_problems_to_another_contest(self, request, queryset):
393393

394394
return redirect('%s?%s' % (base_url, query_string))
395395

396+
def assign_problems_to_a_round(self, request, queryset):
397+
ids = [problem.id for problem in queryset]
398+
399+
# Attach problem ids as arguments to the URL
400+
base_url = reverse('assign_problems_to_a_round')
401+
query_string = urlencode({'ids': ','.join(str(i) for i in ids)}, doseq=True)
402+
403+
return redirect('%s?%s' % (base_url, query_string))
404+
396405
def __init__(self, *args, **kwargs):
397406
# creating a thread local variable to store the request
398407
self._request_local = threading.local()
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
[
2+
{
3+
"pk": "no-rounds",
4+
"model": "contests.contest",
5+
"fields": {
6+
"name": "Test contest",
7+
"controller_name": "oioioi.programs.controllers.ProgrammingContestController",
8+
"creation_date": "2011-07-31T20:27:58.768Z",
9+
"is_archived": "False"
10+
}
11+
},
12+
{
13+
"pk": 100,
14+
"model": "contests.probleminstance",
15+
"fields": {
16+
"problem": 1,
17+
"short_name": "zad1",
18+
"contest": "no-rounds"
19+
}
20+
},
21+
{
22+
"pk": "one-round",
23+
"model": "contests.contest",
24+
"fields": {
25+
"name": "Test contest",
26+
"controller_name": "oioioi.programs.controllers.ProgrammingContestController",
27+
"creation_date": "2011-07-31T20:27:58.768Z",
28+
"is_archived": "False"
29+
}
30+
},
31+
{
32+
"pk": 1,
33+
"model": "contests.round",
34+
"fields": {
35+
"name": "Round 1",
36+
"contest": "one-round",
37+
"start_date": "2011-07-31T20:27:58.768Z",
38+
"results_date": "2012-07-31T20:27:58.768Z"
39+
}
40+
},
41+
{
42+
"pk": 300,
43+
"model": "contests.probleminstance",
44+
"fields": {
45+
"problem": 1,
46+
"short_name": "zad2",
47+
"contest": "one-round"
48+
}
49+
},
50+
{
51+
"pk": 301,
52+
"model": "contests.probleminstance",
53+
"fields": {
54+
"problem": 1,
55+
"short_name": "zad3",
56+
"contest": "one-round"
57+
}
58+
},
59+
60+
{
61+
"pk": "multiple-rounds",
62+
"model": "contests.contest",
63+
"fields": {
64+
"name": "Test contest",
65+
"controller_name": "oioioi.programs.controllers.ProgrammingContestController",
66+
"creation_date": "2011-07-31T20:27:58.768Z",
67+
"is_archived": "False"
68+
}
69+
},
70+
{
71+
"pk": 3,
72+
"model": "contests.round",
73+
"fields": {
74+
"name": "Round 1",
75+
"contest": "multiple-rounds",
76+
"start_date": "2011-07-31T20:27:58.768Z",
77+
"results_date": "2012-07-31T20:27:58.768Z"
78+
}
79+
},
80+
{
81+
"pk": 4,
82+
"model": "contests.round",
83+
"fields": {
84+
"name": "Round 2",
85+
"contest": "multiple-rounds",
86+
"start_date": "2011-07-31T20:27:58.768Z",
87+
"results_date": "2012-07-31T20:27:58.768Z"
88+
}
89+
},
90+
{
91+
"pk": 400,
92+
"model": "contests.probleminstance",
93+
"fields": {
94+
"problem": 1,
95+
"short_name": "zad4",
96+
"contest": "multiple-rounds"
97+
}
98+
},
99+
{
100+
"pk": 401,
101+
"model": "contests.probleminstance",
102+
"fields": {
103+
"problem": 1,
104+
"short_name": "zad5",
105+
"contest": "multiple-rounds"
106+
}
107+
},
108+
{
109+
"pk": 402,
110+
"model": "contests.probleminstance",
111+
"fields": {
112+
"problem": 1,
113+
"short_name": "zad5"
114+
}
115+
}
116+
]
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
[
2+
{
3+
"pk": 1103,
4+
"model": "auth.user",
5+
"fields": {
6+
"username": "test_weak_admin",
7+
"first_name": "Test",
8+
"last_name": "Weak Admin",
9+
"is_active": true,
10+
"is_superuser": false,
11+
"is_staff": false,
12+
"last_login": "2012-07-31T20:27:58.768Z",
13+
"groups": [],
14+
"user_permissions": [],
15+
"password": "",
16+
"email": "[email protected]",
17+
"date_joined": "2012-07-31T20:27:58.768Z"
18+
}
19+
},
20+
{
21+
"pk": "secret_contest",
22+
"model": "contests.contest",
23+
"fields": {
24+
"name": "Secret contest",
25+
"controller_name": "oioioi.programs.controllers.ProgrammingContestController",
26+
"creation_date": "2011-07-31T20:27:58.768Z",
27+
"is_archived": "False"
28+
}
29+
},
30+
{
31+
"pk": "available_contest",
32+
"model": "contests.contest",
33+
"fields": {
34+
"name": "Available contest",
35+
"controller_name": "oioioi.programs.controllers.ProgrammingContestController",
36+
"creation_date": "2011-07-31T20:27:58.768Z",
37+
"is_archived": "False"
38+
}
39+
},
40+
{
41+
"pk": 100,
42+
"model": "contests.round",
43+
"fields": {
44+
"name": "Round 1",
45+
"contest": "available_contest",
46+
"start_date": "2011-07-31T20:27:58.768Z",
47+
"results_date": "2012-07-31T20:27:58.768Z"
48+
}
49+
},
50+
{
51+
"pk": 20,
52+
"model": "contests.contestpermission",
53+
"fields": {
54+
"permission": "contests.contest_basicadmin",
55+
"user": [
56+
"test_weak_admin"
57+
],
58+
"contest": "available_contest"
59+
}
60+
},
61+
{
62+
"pk": 1,
63+
"model": "problems.problem",
64+
"fields": {
65+
"main_problem_instance": 1,
66+
"legacy_name": "Dummy",
67+
"short_name": "dum",
68+
"controller_name": "oioioi.sinolpack.controllers.SinolProblemController",
69+
"package_backend_name": "oioioi.sinolpack.package.SinolPackageBackend"
70+
}
71+
},
72+
{
73+
"pk": 1,
74+
"model": "contests.probleminstance",
75+
"fields": {
76+
"problem": 1,
77+
"short_name": "zad1"
78+
}
79+
},
80+
{
81+
"pk": 1000,
82+
"model": "contests.probleminstance",
83+
"fields": {
84+
"problem": 1,
85+
"contest": "secret_contest",
86+
"short_name": "zad1"
87+
}
88+
},
89+
{
90+
"pk": 1001,
91+
"model": "contests.probleminstance",
92+
"fields": {
93+
"problem": 1,
94+
"short_name": "zad2"
95+
}
96+
},
97+
{
98+
"pk": 1,
99+
"model": "problems.problemsite",
100+
"fields": {
101+
"problem": 1,
102+
"url_key": "123"
103+
}
104+
}
105+
]

oioioi/contests/forms.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,3 +419,19 @@ class SubmissionMessageForm(PublicMessageForm):
419419
class Meta(object):
420420
model = SubmitMessage
421421
fields = ['content']
422+
423+
class RoundSelectionForm(forms.Form):
424+
round = forms.ModelChoiceField(
425+
queryset=Round.objects.none(),
426+
label=_("Round"),
427+
empty_label=_("Select round"),
428+
required=False,
429+
widget=forms.Select(attrs={'class': 'form-control'}),
430+
)
431+
432+
def __init__(self, *args, **kwargs):
433+
contest = kwargs.pop('contest', None)
434+
super(RoundSelectionForm, self).__init__(*args, **kwargs)
435+
if contest is None:
436+
raise ValueError("Contest must be provided to RoundSelectionForm.")
437+
self.fields['round'].queryset = Round.objects.filter(contest=contest)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{% extends "base-with-menu.html" %}
2+
{% load i18n %}
3+
4+
{% block title %}{% trans "Assign problems to a round" %}{% endblock %}
5+
6+
{% block main-content %}
7+
<h2>
8+
{% blocktrans %} Round Choice {% endblocktrans %}
9+
</h2>
10+
<p>
11+
{% blocktrans %}Choose a round to assign the following problems to: {% endblocktrans %}
12+
<ul class="list-group d-inline-block">
13+
{% for problem in problem_instances %}
14+
<li class="list-group-item">{{ problem }}</li>
15+
{% endfor %}
16+
</ul>
17+
</p>
18+
19+
<form method="post">
20+
{% csrf_token %}
21+
<input type="hidden" name="post" value="yes" />
22+
<div class="form-group d-flex">
23+
{{ form.as_p }}
24+
</div>
25+
<div class="form-group">
26+
<button type="submit" class="btn btn-primary">
27+
{% trans "Assign" %}
28+
</button>
29+
<button type="button" class="btn btn-outline-secondary" onclick="history.back()">
30+
{% trans "No, go back" %}
31+
</button>
32+
</div>
33+
</form>
34+
{% endblock %}

0 commit comments

Comments
 (0)