Skip to content

Django 5 migration #483

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

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
dd37ee1
Fix: Use `line.fields|length` in fieldset.html and change GET to POST…
Iteron-dev Feb 26, 2025
af2463b
Fix: Change `pytest.mark.optionalhook` to `pytest.hookimpl(optionalho…
Iteron-dev Mar 5, 2025
0c97651
Fix: Django deprecations (DEFAULT_FILE_STORAGE, index_together)
Iteron-dev Mar 9, 2025
023fd9f
Bump django-compressor from `>=4.3,<4.4` to `>=4.5,<4.6`
Iteron-dev Mar 9, 2025
05ed4ed
Bump django-mptt from `>=0.14,<0.15` to `>=0.16,<0.17` and update pos…
Iteron-dev Mar 10, 2025
9870e92
Fix: `Model instances passed to related filters must be saved` error …
Iteron-dev Mar 11, 2025
691a5ae
Fix: Remove `filter_horizontal` in BaseTagAdmin and bump django to 5.1.7
Iteron-dev Mar 12, 2025
ac99d52
Fix: Django deprecation (log_deletion, log_actions)
Iteron-dev Mar 12, 2025
f3e9b50
Merge branch 'master' into feature/django-5-migration
Iteron-dev Mar 18, 2025
ed88069
Fix: test_can_change_login_from_invalid and test_login_cannot_change_…
Iteron-dev Mar 19, 2025
bbbe8f7
Fix: Add `unlocalize` to progress percentage in task-archive-problem…
Iteron-dev Mar 19, 2025
4339cf3
Refactor: Remove redundant `USE_L10N` in default_settings.py (due dja…
Iteron-dev Mar 19, 2025
01f2ae8
Merge branch 'master' into feature/django-5-migration
Iteron-dev Apr 1, 2025
ce64d04
Fix: Move `DATETIME_FORMAT` to custom format files
Iteron-dev Apr 1, 2025
a706c68
Fix: `TypeError: cannot pickle 'generator' object` error
Iteron-dev Apr 1, 2025
714f6a5
Fix: Replace LogoutView with logout() in `not_anonymous` function
Iteron-dev Apr 1, 2025
93673d5
Fix: TestsSelectionForm
Iteron-dev Apr 2, 2025
95e5567
Fix: Add `request` lookup_allowed() (django deprecation)
Iteron-dev Apr 2, 2025
ae3eb1d
Fix: Add missing migrations (due to index_together deprecation)
Iteron-dev Apr 2, 2025
76f76ae
Fix: Migrations due `index_together` deprecation
Iteron-dev Apr 15, 2025
5842299
Bump django from `5.1.7` to `o>=5.2,<5.3`
Iteron-dev Apr 15, 2025
479d175
Merge branch 'master' into feature/django-5-migration
Iteron-dev Apr 15, 2025
90fc14e
Refactor: Use admin decorators and replace `re_path` with `path` (ass…
Iteron-dev Apr 15, 2025
e1c413d
Refactor: Remove redundant comment from setup.py
Iteron-dev Apr 15, 2025
52e52c8
Docs: Adapt to Django 5.2
Iteron-dev Apr 15, 2025
12afd55
Bump the minimum version of `pytest-django` to `4.11`, and update `dj…
Iteron-dev Apr 16, 2025
9ac67dd
Merge branch 'master' into feature/django-5-migration
Iteron-dev Apr 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ def pytest_collection_modifyitems(config, items):


# Removing links column from html report
@pytest.mark.optionalhook
@pytest.hookimpl(optionalhook=True)
def pytest_html_results_table_header(cells):
cells.pop()


# Removing links column from html report
@pytest.mark.optionalhook
@pytest.hookimpl(optionalhook=True)
def pytest_html_results_table_row(report, cells):
cells.pop()
2 changes: 1 addition & 1 deletion docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ x-common-envs: &common-envs

services:
db:
image: library/postgres:12.2
image: library/postgres:14.17
environment:
POSTGRES_USER: "oioioi"
POSTGRES_PASSWORD: "password"
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ x-common-envs: &common-envs

services:
db:
image: library/postgres:12.2
image: library/postgres:14.17
environment:
POSTGRES_USER: "oioioi"
POSTGRES_PASSWORD: "password"
Expand Down
21 changes: 11 additions & 10 deletions oioioi/balloons/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from django.urls import path
from django.urls import re_path

from oioioi.balloons import views

app_name = 'balloons'

contest_patterns = [
re_path(
r'^balloons/regenerate/$',
path(
'balloons/regenerate/',
views.balloons_regenerate_delivery_key_view,
name='balloons_access_regenerate',
),
Expand All @@ -15,18 +16,18 @@
views.balloons_access_cookie_view,
name='balloons_access_set_cookie',
),
re_path(
r'^balloons/delivery/panel/$',
path(
'balloons/delivery/panel/',
views.balloons_delivery_panel_view,
name='balloons_delivery_panel',
),
re_path(
r'^balloons/delivery/new/$',
path(
'balloons/delivery/new/',
views.get_new_balloon_requests_view,
name='balloons_delivery_new',
),
re_path(
r'^balloons/delivery/set/$',
path(
'balloons/delivery/set/',
views.set_balloon_delivered_view,
name='balloons_set_delivered',
),
Expand All @@ -38,6 +39,6 @@
views.balloon_svg_view,
name='balloon_svg',
),
re_path(r'^balloons/$', views.balloons_view, name='balloons'),
re_path(r'^balloons/body/$', views.balloons_body_view, name='balloons_body'),
path('balloons/', views.balloons_view, name='balloons'),
path('balloons/body/', views.balloons_body_view, name='balloons_body'),
]
16 changes: 9 additions & 7 deletions oioioi/base/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def delete_view(self, request, object_id, extra_context=None):

if request.POST: # The user has already confirmed the deletion.
obj_display = force_str(obj)
self.log_deletion(request, obj, obj_display)
self.log_deletions(request, (obj,))
self.delete_model(request, obj)
self.message_user(
request,
Expand Down Expand Up @@ -177,6 +177,9 @@ def has_view_permission(self, request, obj=None):
return self.has_change_permission(request, obj)


@admin.action(
description=_("Delete selected %(verbose_name_plural)s")
)
def delete_selected(modeladmin, request, queryset, **kwargs):
"""Default ModelAdmin action that deletes the selected objects.

Expand Down Expand Up @@ -212,9 +215,7 @@ def delete_selected(modeladmin, request, queryset, **kwargs):
raise PermissionDenied
n = queryset.count()
if n:
for obj in queryset:
obj_display = force_str(obj)
modeladmin.log_deletion(request, obj, obj_display)
modeladmin.log_deletions(request, queryset)
queryset.delete()
message_text = _("Successfully deleted %(count)d %(items)s.") % {
"count": n,
Expand Down Expand Up @@ -267,7 +268,6 @@ def delete_selected(modeladmin, request, queryset, **kwargs):
)


delete_selected.short_description = _("Delete selected %(verbose_name_plural)s")


def collect_deleted_objects(modeladmin, request, queryset):
Expand Down Expand Up @@ -347,6 +347,7 @@ def login(self, request, extra_context=None):
side_pane_menus_registry.register(system_admin_menu_registry, order=10)


@admin.register(User, site=site)
class OioioiUserAdmin(UserAdmin, ObjectWithMixins, metaclass=ModelAdminMeta):
form = OioioiUserChangeForm
add_form = OioioiUserCreationForm
Expand All @@ -371,13 +372,14 @@ def formfield_for_manytomany(self, db_field, request=None, **kwargs):
kwargs['widget'] = forms.CheckboxSelectMultiple()
return super().formfield_for_manytomany(db_field, request, **kwargs)

@admin.action(
description=_("Mark users as active")
)
def activate_user(self, request, qs):
qs.update(is_active=True)

activate_user.short_description = _("Mark users as active")


site.register(User, OioioiUserAdmin)

system_admin_menu_registry.register(
'users',
Expand Down
6 changes: 3 additions & 3 deletions oioioi/base/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ class EnumField(models.CharField):
description = _("Enumeration")

def __init__(self, registry=None, *args, **kwargs):
self.registry = registry

if registry:
# This allows this field to be stored for migration purposes
# without the need to serialize an EnumRegistry object.
Expand All @@ -225,12 +227,10 @@ def __init__(self, registry=None, *args, **kwargs):
), 'Invalid registry passed to EnumField.__init__: %r' % (registry,)
kwargs['max_length'] = registry.max_length
kwargs['choices'] = self._generate_choices()
self.registry = registry
models.CharField.__init__(self, *args, **kwargs)

def _generate_choices(self):
for item in self.registry.entries:
yield item
return list(self.registry.entries)

def deconstruct(self):
name, path, args, kwargs = super(EnumField, self).deconstruct()
Expand Down
3 changes: 2 additions & 1 deletion oioioi/base/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.contrib.auth.views import LogoutView, redirect_to_login
from django.core.exceptions import PermissionDenied
from django.template.response import TemplateResponse
from django.contrib.auth import logout

from oioioi.base.utils import is_ajax

Expand Down Expand Up @@ -168,7 +169,7 @@ def not_anonymous(request):
if request.user.is_active:
return True
else:
LogoutView.as_view()(request)
logout(request)
return False
else:
return False
Expand Down
23 changes: 12 additions & 11 deletions oioioi/base/registration_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from django.contrib.auth.models import User
from django.contrib.auth.views import PasswordResetView, PasswordResetDoneView
from django.contrib.sites.requests import RequestSite
from django.urls import re_path, reverse_lazy
from django.urls import path
from django.urls import reverse_lazy
from django.views.generic import TemplateView, RedirectView
from registration import signals
from registration.backends.default.views import (
Expand Down Expand Up @@ -45,7 +46,7 @@ def register(self, form):
user.id,
user.username,
request.META.get('REMOTE_ADDR', '?'),
request.META.get('HTTP_USER_AGENT', '?'),
request.headers.get('user-agent', '?'),
)

registration_profile = RegistrationProfile.objects.create_profile(user)
Expand All @@ -63,24 +64,24 @@ def register(self, form):


urlpatterns = [
re_path(
r'^register/$',
path(
'register/',
RedirectView.as_view(pattern_name='sign-up', permanent=True),
name='registration_redirect',
),
]

if not settings.SEND_USER_ACTIVATION_EMAIL:
urlpatterns += [
re_path(
r'^sign-up/$',
path(
'sign-up/',
RegistrationView.as_view(
success_url=reverse_lazy('sign-up_complete')
),
name='sign-up',
),
re_path(
r'^sign-up/complete/$',
path(
'sign-up/complete/',
TemplateView.as_view(
template_name='registration/'
'registration_and_activation_complete.html'
Expand All @@ -90,12 +91,12 @@ def register(self, form):
]
else:
urlpatterns += [
re_path(r'^sign-up/$', RegistrationView.as_view(), name='sign-up')
path('sign-up/', RegistrationView.as_view(), name='sign-up')
]

urlpatterns += [
re_path(
r'^password/reset/$',
path(
'password/reset/',
PasswordResetView.as_view(
form_class=OioioiPasswordResetForm,
success_url=reverse_lazy('auth_password_reset_done'),
Expand Down
4 changes: 2 additions & 2 deletions oioioi/base/templates/admin/includes/fieldset.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
{% for line in fieldset %}
<div class="form-group{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% if not line.has_visible_field %} d-none{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
<div class="form-group{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} d-none{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
{% for field in line %}
<div{% if not line.fields|length_is:'1' %} class="fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} d-none{% endif %}"{% elif field.is_checkbox %} class="form-check checkbox-row"{% endif %}>
<div{% if not line.fields|length == 1 %} class="fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} d-none{% endif %}"{% elif field.is_checkbox %} class="form-check checkbox-row"{% endif %}>
{% if field.is_checkbox %}
{% if field.errors %}
{{ field.field | add_class:"form-check-input" | add_class:"is-invalid" }}
Expand Down
22 changes: 11 additions & 11 deletions oioioi/base/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ def custom_login(*args, **kwargs):
self.client.login = custom_login

def ajax_get(self, url):
return self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
return self.client.get(url, headers={"x-requested-with": 'XMLHttpRequest'})

def assertHtml(self, response, code):
self.assertContains(response, '<html', status_code=code)
Expand Down Expand Up @@ -1267,9 +1267,9 @@ def test_can_change_login_from_invalid(self):
# The html strings underneath may change with any django upgrade.
self.assertContains(
response,
'<input type="text" id="id_username" name="username" '
'value="%s" class="form-control" '
'maxlength="150" required />' % l,
'<input type="text" name="username" value="%s" '
'maxlength="150" class="form-control" required="" '
'aria-describedby="id_username_helptext" id="id_username">' % l,
html=True,
)

Expand All @@ -1286,9 +1286,9 @@ def test_can_change_login_from_invalid(self):
response = self.client.get(self.url_edit_profile)
self.assertContains(
response,
'<input type="text" id="id_username" name="username" '
'value="valid_user" class="form-control" '
'maxlength="150" readonly required />',
'<input type="text" name="username" value="valid_user" '
'maxlength="150" readonly="" class="form-control" required="" '
'aria-describedby="id_username_helptext" id="id_username">',
html=True,
)

Expand All @@ -1300,9 +1300,9 @@ def test_login_cannot_change_from_valid(self):
response = self.client.get(self.url_edit_profile)
self.assertContains(
response,
'<input type="text" id="id_username" name="username" '
'value="%s" class="form-control" '
'maxlength="150" readonly required />' % l,
'<input type="text" name="username" value="%s" '
'maxlength="150" readonly="" class="form-control" required="" '
'aria-describedby="id_username_helptext" id="id_username">' % l,
html=True,
)

Expand Down Expand Up @@ -1454,7 +1454,7 @@ def test_invalidated_user_logout(self):
self.user.is_active = False
self.user.save()

self.client.get(self.profile_index, follow=True)
self.client.post(self.profile_index, follow=True)
# At this point we should check if user is deactivated and log him out.
self.assert_logged(False)

Expand Down
25 changes: 13 additions & 12 deletions oioioi/base/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.conf import settings
from django.urls import path
from django.urls import include, re_path
from oioioi.base import admin, api, views
from oioioi.base.main_page import main_page_view
Expand All @@ -8,27 +9,27 @@
app_name = 'base'

urlpatterns = [
re_path(r'', include(tf_urls)),
re_path(r'^force_error/$', views.force_error_view, name='force_error'),
re_path(
r'^force_permission_denied/$',
path('', include(tf_urls)),
path('force_error/', views.force_error_view, name='force_error'),
path(
'force_permission_denied/',
views.force_permission_denied_view,
name='force_permission_denied',
),
re_path(r'^edit_profile/$', views.edit_profile_view, name='edit_profile'),
re_path(r'^logout/$', views.logout_view, name='logout'),
re_path(r'^translate/$', views.translate_view, name='translate'),
re_path(r'^login/$', views.login_view, name='login'),
re_path(r'^delete_account/$', views.delete_account_view, name='delete_account'),
re_path(r'^generate_key/$', views.generate_key_view, name='generate_key'),
path('edit_profile/', views.edit_profile_view, name='edit_profile'),
path('logout/', views.logout_view, name='logout'),
path('translate/', views.translate_view, name='translate'),
path('login/', views.login_view, name='login'),
path('delete_account/', views.delete_account_view, name='delete_account'),
path('generate_key/', views.generate_key_view, name='generate_key'),
# don't include without appropriate patching! admincdocs provides its own
# login view which can be used to bypass 2FA.
# re_path(r'^admin/doc/', include('django.contrib.admindocs.urls')),
re_path(r'^admin/logout/$', views.logout_view),
path('admin/logout/', views.logout_view),
]

urlpatterns += [
re_path(r'^$', main_page_view, name='index'),
path('', main_page_view, name='index'),
]

noncontest_patterns = [
Expand Down
4 changes: 2 additions & 2 deletions oioioi/clock/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from django.urls import re_path
from django.urls import path

from oioioi.clock import views

app_name = 'clock'

urlpatterns = [
# Don't use the 'admin/' prefix, as that sometimes results in breakage.
re_path(r'^admin_time/$', views.admin_time, name='admin_time'),
path('admin_time/', views.admin_time, name='admin_time'),
]
6 changes: 3 additions & 3 deletions oioioi/complaints/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from django.urls import re_path
from django.urls import path

from oioioi.complaints import views

app_name = 'complaints'

contest_patterns = [
re_path(r'^complaints/$', views.add_complaint_view, name='add_complaint'),
re_path(r'^complaint_sent/$', views.complaint_sent, name='complaint_sent'),
path('complaints/', views.add_complaint_view, name='add_complaint'),
path('complaint_sent/', views.complaint_sent, name='complaint_sent'),
]
Loading