diff --git a/oioioi/base/tests/tests.py b/oioioi/base/tests/tests.py index f3948eecf..2f5d68a6e 100644 --- a/oioioi/base/tests/tests.py +++ b/oioioi/base/tests/tests.py @@ -61,6 +61,7 @@ from oioioi.contests.models import Contest from oioioi.contests.utils import is_contest_admin from oioioi.szkopul.views import main_page_view as szkopul_main_page +from oioioi.welcomepage.views import welcome_page_view if not getattr(settings, 'TESTS', False): print( @@ -163,6 +164,7 @@ class TestIndexNoContest(TestCase): @override_settings(DEFAULT_GLOBAL_PORTAL_AS_MAIN_PAGE=False) def test_no_contest(self): + unregister_main_page_view(welcome_page_view) unregister_main_page_view(szkopul_main_page) with self.assertNumQueriesLessThan(50): response = self.client.get('/') diff --git a/oioioi/deployment/settings.py.template b/oioioi/deployment/settings.py.template index 4a28b1f9c..8e20cea92 100755 --- a/oioioi/deployment/settings.py.template +++ b/oioioi/deployment/settings.py.template @@ -196,6 +196,7 @@ INSTALLED_APPS = ( # 'oioioi.problemsharing', # 'oioioi.usergroups', # 'oioioi.usercontests', + # 'oioioi.welcomepage', ) + INSTALLED_APPS # If set to locations of flite and sox executables, enables audio playback @@ -580,5 +581,3 @@ ZEUS_INSTANCES = { # Experimental # USE_ACE_EDITOR = False - - diff --git a/oioioi/szkopul/tests.py b/oioioi/szkopul/tests.py index c81eb2ea6..87c96aa4e 100644 --- a/oioioi/szkopul/tests.py +++ b/oioioi/szkopul/tests.py @@ -7,10 +7,12 @@ from django.test.utils import override_settings from django.urls import NoReverseMatch, reverse +from oioioi.base.main_page import unregister_main_page_view from oioioi.base.tests import TestCase from oioioi.contests.current_contest import ContestMode from oioioi.contests.models import ProblemInstance, Submission from oioioi.problems.models import Problem +from oioioi.welcomepage.views import welcome_page_view class TestMainPageView(TestCase): @@ -23,6 +25,10 @@ class TestMainPageView(TestCase): 'test_submission', ] + def setUp(self): + super(TestMainPageView, self).setUp() + unregister_main_page_view(welcome_page_view) + @override_settings(CONTEST_MODE=ContestMode.neutral) def test_navbar_links(self): try: diff --git a/oioioi/test_settings.py b/oioioi/test_settings.py index 9ae674474..cdcc6999e 100644 --- a/oioioi/test_settings.py +++ b/oioioi/test_settings.py @@ -61,6 +61,7 @@ 'oioioi.problemsharing', 'oioioi.usercontests', 'oioioi.mp', + 'oioioi.welcomepage', ) + INSTALLED_APPS TEMPLATES[0]['OPTIONS']['context_processors'] += [ diff --git a/oioioi/welcomepage/README.rst b/oioioi/welcomepage/README.rst new file mode 100644 index 000000000..a2e292a95 --- /dev/null +++ b/oioioi/welcomepage/README.rst @@ -0,0 +1 @@ +Welcome page with custom message which is the default page if the app is enabled. diff --git a/oioioi/welcomepage/__init__.py b/oioioi/welcomepage/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/oioioi/welcomepage/apps.py b/oioioi/welcomepage/apps.py new file mode 100644 index 000000000..7b3958de8 --- /dev/null +++ b/oioioi/welcomepage/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class WelcomePageAppConfig(AppConfig): + default_auto_field = 'django.db.models.AutoField' + name = "oioioi.welcomepage" diff --git a/oioioi/welcomepage/forms.py b/oioioi/welcomepage/forms.py new file mode 100644 index 000000000..4ba10cbf2 --- /dev/null +++ b/oioioi/welcomepage/forms.py @@ -0,0 +1,37 @@ +from django import forms +from django.conf import settings +from django.forms import modelformset_factory + +from oioioi.welcomepage.models import WelcomePageMessage + + +class WelcomePageMessageForm(forms.ModelForm): + class Meta(object): + model = WelcomePageMessage + fields = ['content', 'language'] + + language = forms.ChoiceField( + label="Language", + choices=settings.LANGUAGES, + widget=forms.HiddenInput(), + ) + + content = forms.CharField( + label="Content", + widget=forms.Textarea(attrs={ + 'rows': 10, + 'style': 'white-space: pre;', + 'class': 'monospace', + }), + ) + +WelcomePageMessageFormset = modelformset_factory( + WelcomePageMessage, + form=WelcomePageMessageForm, + extra=len(settings.LANGUAGES), + min_num=1, + max_num=len(settings.LANGUAGES), + validate_min=True, + validate_max=True, + can_delete=True, +) diff --git a/oioioi/welcomepage/migrations/0001_initial.py b/oioioi/welcomepage/migrations/0001_initial.py new file mode 100644 index 000000000..3f136780a --- /dev/null +++ b/oioioi/welcomepage/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.16 on 2024-09-06 09:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='WelcomePageMessage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField(blank=True, verbose_name='message')), + ('language', models.CharField(max_length=6, verbose_name='language code')), + ], + options={ + 'verbose_name': 'welcome page message', + 'verbose_name_plural': 'welcome page messages', + }, + ), + ] diff --git a/oioioi/welcomepage/migrations/__init__.py b/oioioi/welcomepage/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/oioioi/welcomepage/models.py b/oioioi/welcomepage/models.py new file mode 100644 index 000000000..164ae12d8 --- /dev/null +++ b/oioioi/welcomepage/models.py @@ -0,0 +1,15 @@ +from django.db import models +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ + + +class WelcomePageMessage(models.Model): + content = models.TextField(verbose_name=_("message"), blank=True) + language = models.CharField(max_length=6, verbose_name=_("language code")) + + class Meta: + verbose_name = _("welcome page message") + verbose_name_plural = _("welcome page messages") + + def render_content(self): + return mark_safe(self.content) diff --git a/oioioi/welcomepage/static/welcomepage/textfield-tab.js b/oioioi/welcomepage/static/welcomepage/textfield-tab.js new file mode 100644 index 000000000..e61d62060 --- /dev/null +++ b/oioioi/welcomepage/static/welcomepage/textfield-tab.js @@ -0,0 +1,9 @@ +$('textarea').on('keydown', function (e) { + if (e.key === 'Tab') { + e.preventDefault(); + const start = this.selectionStart; + const end = this.selectionEnd; + this.value = this.value.substring(0, start) + '\t' + this.value.substring(end); + this.selectionStart = this.selectionEnd = start + 1; + } +}); diff --git a/oioioi/welcomepage/templates/welcomepage/welcome-page-edit.html b/oioioi/welcomepage/templates/welcomepage/welcome-page-edit.html new file mode 100644 index 000000000..e8f8fe0a9 --- /dev/null +++ b/oioioi/welcomepage/templates/welcomepage/welcome-page-edit.html @@ -0,0 +1,31 @@ +{% extends "base-with-menu.html" %} +{% load i18n static %} + +{% block title %}Edit welcome page content{% endblock %} + +{% block main-content %} +

Edit welcome page content

+
+ {% csrf_token %} + {% include "ingredients/translation-formset.html" %} +
+ + {% trans "Delete all messages" %} + +
+ +
+ +
+ +
+ + + +{% endblock %} diff --git a/oioioi/welcomepage/templates/welcomepage/welcome-page.html b/oioioi/welcomepage/templates/welcomepage/welcome-page.html new file mode 100644 index 000000000..b2e0ec823 --- /dev/null +++ b/oioioi/welcomepage/templates/welcomepage/welcome-page.html @@ -0,0 +1,20 @@ +{% extends "simple-centered.html" %} +{% load i18n %} + +{% block title %}{{ title }}{% endblock %} + +{% block content %} + +{{ welcome_page_msg.render_content }} + +
+ {% if show_edit_button %} +
+ + {% trans "Edit message" %} + +
+ {% endif %} +
+ +{% endblock %} diff --git a/oioioi/welcomepage/tests.py b/oioioi/welcomepage/tests.py new file mode 100644 index 000000000..8bc0c8009 --- /dev/null +++ b/oioioi/welcomepage/tests.py @@ -0,0 +1,60 @@ +from oioioi.base.main_page import unregister_main_page_view +from oioioi.base.tests import TestCase +from django.urls import reverse + +from oioioi.welcomepage.models import WelcomePageMessage + + +class TestWelcomePage(TestCase): + fixtures = ['test_users'] + + def test_button_visibility(self): + WelcomePageMessage.objects.create(language='en', content='Welcome to OIOIOI!') + + self.assertTrue(self.client.login(username='test_admin')) + response = self.client.get(reverse('welcome_page')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'Edit message') + + self.assertTrue(self.client.login(username='test_user')) + response = self.client.get(reverse('welcome_page')) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, 'Edit message') + + response = self.client.get(reverse('edit_welcome_page')) + self.assertEqual(response.status_code, 403) + + def test_no_message(self): + self.assertTrue(self.client.login(username='test_user')) + response = self.client.get(reverse('welcome_page')) + self.assertEqual(response.status_code, 403) + + def test_message(self): + self.assertTrue(self.client.login(username='test_user')) + msgs = { + 'pl': 'Witaj na OIOIOI!', + 'en': 'Welcome to OIOIOI!' + } + for lang, msg in msgs.items(): + WelcomePageMessage.objects.create(language=lang, content=msg) + + for lang, msg in msgs.items(): + self.client.cookies['lang'] = lang + response = self.client.get(reverse('welcome_page')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, msg) + + def test_one_language(self): + self.assertTrue(self.client.login(username='test_user')) + msg = 'Welcome to OIOIOI!' + WelcomePageMessage.objects.create(language='en', content=msg) + + self.client.cookies['lang'] = 'en' + response = self.client.get(reverse('welcome_page')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, msg) + + self.client.cookies['lang'] = 'pl' + response = self.client.get(reverse('welcome_page')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, msg) diff --git a/oioioi/welcomepage/urls.py b/oioioi/welcomepage/urls.py new file mode 100644 index 000000000..36aecd64a --- /dev/null +++ b/oioioi/welcomepage/urls.py @@ -0,0 +1,11 @@ +from django.urls import re_path + +from oioioi.welcomepage import views + +app_name = 'welcomepage' + +noncontest_patterns = [ + re_path(r'^welcome/$', views.welcome_page_view, name='welcome_page'), + re_path(r'^edit_welcome_page/$', views.edit_welcome_page_view, name='edit_welcome_page'), + re_path(r'^delete_welcome_page/$', views.delete_welcome_page_view, name='delete_welcome_page'), +] diff --git a/oioioi/welcomepage/utils.py b/oioioi/welcomepage/utils.py new file mode 100644 index 000000000..a09aa105f --- /dev/null +++ b/oioioi/welcomepage/utils.py @@ -0,0 +1,7 @@ +from oioioi.base.permissions import make_request_condition +from oioioi.welcomepage.models import WelcomePageMessage + + +@make_request_condition +def any_welcome_messages(request): + return WelcomePageMessage.objects.exists() diff --git a/oioioi/welcomepage/views.py b/oioioi/welcomepage/views.py new file mode 100644 index 000000000..57b0fe0d6 --- /dev/null +++ b/oioioi/welcomepage/views.py @@ -0,0 +1,77 @@ +from django.urls import reverse +from django.conf import settings +from django.shortcuts import redirect +from django.template.response import TemplateResponse +from django.utils.translation import gettext_lazy as _ +from django.utils.translation import get_language_from_request + +from oioioi.base.admin import system_admin_menu_registry +from oioioi.base.main_page import register_main_page_view +from oioioi.base.permissions import enforce_condition, is_superuser +from oioioi.welcomepage.forms import WelcomePageMessageFormset +from oioioi.welcomepage.models import WelcomePageMessage +from oioioi.welcomepage.utils import any_welcome_messages + + +@register_main_page_view(order=110, condition=any_welcome_messages) +@enforce_condition(any_welcome_messages) +def welcome_page_view(request): + current_language = get_language_from_request(request) + try: + welcome_page_msg = WelcomePageMessage.objects.get(language=current_language) + except WelcomePageMessage.DoesNotExist: + welcome_page_msg = WelcomePageMessage.objects.first() + return TemplateResponse( + request, + 'welcomepage/welcome-page.html', + { + 'title': _('Welcome to %(site_name)s') % {'site_name': settings.SITE_NAME}, + 'welcome_page_msg': welcome_page_msg, + 'show_edit_button': is_superuser(request), + }, + ) + + +system_admin_menu_registry.register( + 'welcome_page', + _("Edit welcome page"), + lambda request: reverse('edit_welcome_page'), + order=80, +) +@enforce_condition(is_superuser) +def edit_welcome_page_view(request): + if request.method == 'POST': + formset = WelcomePageMessageFormset(request.POST) + if formset.is_valid(): + instances = formset.save(commit=False) + for instance in instances: + instance.save() + for instance in formset.deleted_objects: + instance.delete() + return redirect('welcome_page') + else: + current_language = get_language_from_request(request) + instances = WelcomePageMessage.objects.all() + languages = [lang_short for lang_short, _ in settings.LANGUAGES] + for instance in instances: + languages.remove(instance.language) + formset = WelcomePageMessageFormset( + initial=[ + {'language': lang, 'DELETE': lang != current_language} + for lang in languages + ], + queryset=instances, + ) + return TemplateResponse( + request, + 'welcomepage/welcome-page-edit.html', + { + 'formset': formset + }, + ) + + +@enforce_condition(is_superuser) +def delete_welcome_page_view(request): + WelcomePageMessage.objects.all().delete() + return redirect(reverse('index'))