Skip to content

Commit

Permalink
Merge pull request #596 from MAKENTNU/dependabot/pip/dev/django-4.1.7
Browse files Browse the repository at this point in the history
⬆(deps): Bump django from 4.0.7 to 4.1.7
  • Loading branch information
ddabble committed Feb 27, 2023
2 parents a70723a + c1355df commit e6e01c8
Show file tree
Hide file tree
Showing 19 changed files with 100 additions and 37 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@ A summary of changes made to the codebase, grouped per deployment.

### Improvements

- Updated Django to version 4.1
- Place files uploaded through CKEditor in a separate folder for each model
- Made all pages have a consistent (browser tab) title format
- Most pages will have " | MAKE NTNU" as suffix to the title;
Expand All @@ -19,10 +20,13 @@ A summary of changes made to the codebase, grouped per deployment.
### Fixes

- Made the CKEditor file uploader work on all subdomains
- Prevented [CSRF attacks](https://owasp.org/www-community/attacks/csrf) against the logout URL,
by requiring logout requests being sent using `POST` instead of `GET`

### Other changes

- Set minimum required Python version to 3.10
- Changed order of the apps listed on [the Django admin index page](https://admin.makentnu.no/)
- Never-ending masses of code cleanup


Expand Down
22 changes: 11 additions & 11 deletions contentbox/tests/urls/hosts.py
@@ -1,9 +1,18 @@
from django.conf import settings
from django.contrib.auth.views import SuccessURLAllowedHostsMixin
from django_hosts import host

from web.hosts import host_patterns as base_host_patterns
from web.settings import generate_all_hosts

# (Changing settings should be done before importing other parts of our code, as it might use these settings
# - like the login path in `web.urls`)
# Set the subdomain and host settings again, as more subdomains are added to `host_patterns` below.
settings.ALL_SUBDOMAINS = (
*settings.ALL_SUBDOMAINS,
'test-internal', 'test-main',
)
settings.ALLOWED_REDIRECT_HOSTS = generate_all_hosts(settings.ALL_SUBDOMAINS)

from web.hosts import host_patterns as base_host_patterns
from . import urls_internal, urls_main


Expand All @@ -12,14 +21,5 @@
host(r"test-main", urls_main.__name__, name='test_main'),
]

# Set the subdomain and host settings again, as we have added more subdomains to `host_patterns`
settings.ALL_SUBDOMAINS = (
*settings.ALL_SUBDOMAINS,
'test-internal', 'test-main',
)
settings.ALLOWED_REDIRECT_HOSTS = generate_all_hosts(settings.ALL_SUBDOMAINS)
# [See the comment in `web/hosts.py`]
SuccessURLAllowedHostsMixin.success_url_allowed_hosts = set(settings.ALLOWED_REDIRECT_HOSTS)

# Makes sure that the subdomain of all requests is `test-internal`
TEST_INTERNAL_CLIENT_DEFAULTS = {'SERVER_NAME': f'test-internal.{settings.PARENT_HOST}'}
2 changes: 1 addition & 1 deletion dataporten/tests/test_views.py
Expand Up @@ -33,7 +33,7 @@ def test_logout(self):
self.user = User.objects.create_user(username=username, password=password)
self.client.login(username=username, password=password)
self.assertTrue(get_user(self.client).is_authenticated)
self.client.get(reverse('logout'))
self.client.post(reverse('logout'))
self.assertFalse(get_user(self.client).is_authenticated)

@mock_module_attrs({
Expand Down
2 changes: 1 addition & 1 deletion dataporten/views.py
Expand Up @@ -15,7 +15,7 @@

class Logout(View):

def get(self, request):
def post(self, request):
logout(request)
return HttpResponseRedirect(settings.LOGOUT_URL)

Expand Down
7 changes: 4 additions & 3 deletions internal/templates/internal/header.html
Expand Up @@ -43,9 +43,10 @@
<div class="text">{% translate "Django admin" %}</div>
</a>
{% endif %}
<a class="item" href="{% host_url 'logout' host 'main' %}">
<div class="text">{% translate "Log out" %}</div>
</a>
<form class="logout-form item" method="POST" action="{% host_url 'logout' host 'main' %}">
{% csrf_token %}
<button class="text" type="submit">{% translate "Log out" %}</button>
</form>
{% endblock user_dropdown_buttons %}

{% block announcements %}{% endblock announcements %}
4 changes: 2 additions & 2 deletions make_queue/forms.py
Expand Up @@ -141,7 +141,7 @@ class Meta:

class Media:
js = (
JS('make_queue/js/quota_form.js', attrs={'defer': 'defer'}),
JS('make_queue/js/quota_form.js', attrs={'defer': True}),
)

def clean(self):
Expand Down Expand Up @@ -288,7 +288,7 @@ def clean(self):
class CreateMachineForm(MachineFormBase):
class Media:
js = (
JS('make_queue/js/machine_create.js', attrs={'defer': 'defer'}),
JS('make_queue/js/machine_create.js', attrs={'defer': True}),
)

def __init__(self, *args, **kwargs):
Expand Down
3 changes: 2 additions & 1 deletion make_queue/models/reservation.py
Expand Up @@ -287,7 +287,8 @@ class Day(models.IntegerChoices):
start_time = models.TimeField(verbose_name=_("start time"))
end_time = models.TimeField(verbose_name=_("end time"))
days_changed = models.IntegerField(verbose_name=_("days"), help_text=_("Number of times midnight is passed between start and end time."))
start_days = MultiSelectField(choices=Day.choices, min_choices=1, verbose_name=_("start days for rule periods"))
# TODO: remove the explicitly set `max_length` when https://github.com/goinnn/django-multiselectfield/issues/131 is resolved
start_days = MultiSelectField(choices=Day.choices, min_choices=1, max_length=13, verbose_name=_("start days for rule periods"))
max_hours = models.FloatField(verbose_name=_("hours single period"))
max_inside_border_crossed = models.FloatField(verbose_name=_("hours multi-period"))
last_modified = models.DateTimeField(auto_now=True, verbose_name=_("last modified"))
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
@@ -1,5 +1,5 @@
# Django-related packages
Django==4.0.7
Django==4.1.7
django-hosts==5.2
django-ckeditor==6.5.1
django-cleanup==6.0.0
Expand Down
34 changes: 34 additions & 0 deletions web/admin.py
@@ -0,0 +1,34 @@
from django.contrib import admin


class WebAdminSite(admin.AdminSite):
apps_listed_first = (
'users',
'groups',
'contentbox',
'announcements',
'news',
'make_queue',
'makerspace',
'faq',
# Apps belonging on other subdomains
'internal',
'docs',
# Non-used (or rarely used) apps
'checkin',
'auth',
)
_apps__to__index = {app: i for i, app in enumerate(apps_listed_first)}

def get_app_list(self, request, app_label=None):
app_list = super().get_app_list(request, app_label=app_label)

sort_last_key = len(self.apps_listed_first)

def app_sorting_key(app_dict: dict):
# Sorts the apps so that those whose labels are in `apps_listed_first` are listed first,
# and the rest are sorted last (keeping their original order)
return self._apps__to__index.get(app_dict['app_label'], sort_last_key)

app_list.sort(key=app_sorting_key)
return app_list
5 changes: 5 additions & 0 deletions web/apps.py
@@ -1,4 +1,5 @@
from django.apps import AppConfig
from django.contrib.admin.apps import AdminConfig


class WebConfig(AppConfig):
Expand All @@ -12,3 +13,7 @@ def ready(self):

# Register / connect to the signals here when the app starts
signals.connect()


class WebAdminConfig(AdminConfig):
default_site = 'web.admin.WebAdminSite'
4 changes: 0 additions & 4 deletions web/hosts.py
@@ -1,5 +1,4 @@
from django.conf import settings
from django.contrib.auth.views import SuccessURLAllowedHostsMixin
from django_hosts import host


Expand All @@ -9,6 +8,3 @@
host(r"docs", 'docs.urls', name='docs'),
host(r"", settings.ROOT_URLCONF, name='main'),
]

# This allows the next parameter in login to redirect to pages on all the subdomains
SuccessURLAllowedHostsMixin.success_url_allowed_hosts = set(settings.ALLOWED_REDIRECT_HOSTS)
2 changes: 1 addition & 1 deletion web/management/commands/runserver.py
Expand Up @@ -10,7 +10,7 @@ class Command(runserver.Command):
Overrides the ``runserver`` management command.
It's currently extending ``daphne``'s command, as it's the one we would normally be using.
This requires that the ``web`` app is listed before other apps that override ``runserver``, in the ``INSTALLED_APPS`` setting.
This requires that ``web.apps.WebConfig`` is listed before other apps that override ``runserver``, in the ``INSTALLED_APPS`` setting.
"""

def inner_run(self, *args, **options):
Expand Down
2 changes: 1 addition & 1 deletion web/multilingual/widgets.py
Expand Up @@ -18,7 +18,7 @@ class MultiLingualTextEdit(forms.MultiWidget):

class Media:
js = (
JS('web/js/forms/widgets/multi_lingual_text_field.js', attrs={'defer': 'defer'}),
JS('web/js/forms/widgets/multi_lingual_text_field.js', attrs={'defer': True}),
)

def __init__(self, attrs=None, *, languages=None, subwidget_kwargs: dict[str, Any] = None):
Expand Down
14 changes: 10 additions & 4 deletions web/settings.py
Expand Up @@ -71,28 +71,29 @@


INSTALLED_APPS = [
# The main entrypoint app; should be listed first, to be able to override things like management commands
'web',
# App used for things regarding the whole project or across other apps
# (Should be listed first, to be able to override things like management commands)
'web.apps.WebConfig',

# Should be listed before `django.contrib.staticfiles`
# (see https://channels.readthedocs.io/en/stable/releases/4.0.0.html#decoupling-of-the-daphne-application-server)
'daphne',

# Built-in Django apps
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'web.apps.WebAdminConfig', # replaces 'django.contrib.admin'

# Third-party packages with significant effect on Django's functionality
'django_hosts',
'channels',

# Other third-party packages
'social_django',
'ckeditor', # must be listed after `web` to make the custom `ckeditor/config.js` apply
'ckeditor', # must be listed after `web.apps.WebConfig` to make the custom `ckeditor/config.js` apply
'ckeditor_uploader',
'phonenumber_field',
'simple_history',
Expand Down Expand Up @@ -196,6 +197,9 @@ def generate_all_hosts(subdomains):
},
]

# TODO: should be removed when upgrading to Django 5.0 (see https://docs.djangoproject.com/en/4.1/releases/4.1/#forms)
FORM_RENDERER = 'django.forms.renderers.DjangoDivFormRenderer'

ASGI_APPLICATION = 'web.asgi.application'
CHANNEL_LAYERS = {
'default': {
Expand Down Expand Up @@ -254,6 +258,8 @@ def generate_all_hosts(subdomains):

# Dataporten

USES_DATAPORTEN_AUTH = SOCIAL_AUTH_DATAPORTEN_KEY and SOCIAL_AUTH_DATAPORTEN_SECRET # (custom setting)

SOCIAL_AUTH_DATAPORTEN_FEIDE_SSL_PROTOCOL = True
SOCIAL_AUTH_LOGIN_REDIRECT_URL = reverse_lazy('front_page')
SOCIAL_AUTH_NEW_USER_REDIRECT_URL = reverse_lazy('front_page')
Expand Down
2 changes: 1 addition & 1 deletion web/static/ckeditor/ckeditor/config.js
@@ -1,5 +1,5 @@
/*
Note: To make this configuration file apply, the `ckeditor` app must be listed after `web` in `INSTALLED_APPS`.
Note: To make this configuration file apply, the `ckeditor` app must be listed after `web.apps.WebConfig` in `INSTALLED_APPS`.
*/
// `CKEDITOR_CONFIG_FROM_DJANGO` is defined in `config_from_django.js`
// noinspection ES6ConvertVarToLetConst
Expand Down
10 changes: 10 additions & 0 deletions web/static/web/css/header.css
Expand Up @@ -159,6 +159,16 @@
background-color: rgba(255, 255, 255, 0.08) !important; /* Overrides Fomantic-UI */
}

#side-nav .logout-form button[type=submit] {
width: 100%;
height: 100%;
text-align: start;
border: none;
padding: 0;
background: none;
cursor: pointer;
}

#side-nav .set-language-form {
position: absolute;
margin: 0;
Expand Down
7 changes: 4 additions & 3 deletions web/templates/web/header.html
Expand Up @@ -115,9 +115,10 @@
<a class="item" href="{% host_url 'my_tickets_list' host 'main' %}">
<div class="text">{% translate "My tickets" %}</div>
</a>
<a class="item" href="{% host_url 'logout' host 'main' %}?next={{ request.path }}">
<div class="text">{% translate "Log out" %}</div>
</a>
<form class="logout-form item" method="POST" action="{% host_url 'logout' host 'main' %}?next={{ request.path }}">
{% csrf_token %}
<button class="text" type="submit">{% translate "Log out" %}</button>
</form>
{% endblock user_dropdown_buttons %}
</nav>

Expand Down
9 changes: 7 additions & 2 deletions web/urls.py
Expand Up @@ -73,7 +73,7 @@
)

# Configure login based on if we have configured Dataporten or not.
if settings.SOCIAL_AUTH_DATAPORTEN_SECRET:
if settings.USES_DATAPORTEN_AUTH:
urlpatterns += i18n_patterns(
path("login/", RedirectView.as_view(url="/login/dataporten/", query_string=True), name='login'),
path("logout/", Logout.as_view(), name='logout'),
Expand All @@ -88,7 +88,12 @@
# If it is not configured, we would like to have a simple login page. So that
# we can test with non-superusers without giving them access to the admin page.
urlpatterns += i18n_patterns(
path("login/", auth_views.LoginView.as_view(template_name='web/login.html', redirect_authenticated_user=True), name='login'),
path("login/", auth_views.LoginView.as_view(
template_name='web/login.html',
redirect_authenticated_user=True,
# This allows the `next` query parameter (used when logging in) to redirect to pages on all the subdomains
success_url_allowed_hosts=set(settings.ALLOWED_REDIRECT_HOSTS),
), name='login'),
path("logout/", auth_views.LogoutView.as_view(next_page="/"), name='logout'),

prefix_default_language=False,
Expand Down
2 changes: 1 addition & 1 deletion web/widgets.py
Expand Up @@ -234,7 +234,7 @@ def __init__(self, *args, **kwargs):
@property
def media(self):
config_data_attrs = {
# Boolean values should be converted to strings (for use by JavaScript), as the `JS` class does not properly support boolean attributes
# Boolean values should be converted to strings, to make it easier for JavaScript code to parse the data attributes
'should-allow-all-tags': json.dumps(self.config_name == settings.CKEDITOR_EDIT_SOURCE_CONFIG_NAME),
}
return forms.Media(
Expand Down

0 comments on commit e6e01c8

Please sign in to comment.