From 05062633dfc8354388cefc3243d03eae79a7ee01 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Wed, 13 Mar 2024 13:41:28 +0100 Subject: [PATCH 001/195] Show image preview to organisers closes #1079 --- doc/changelog.rst | 3 ++ .../cfp/event/user_submission_edit.html | 3 ++ src/pretalx/common/forms/widgets.py | 2 ++ .../templates/common/widgets/image_input.html | 13 ++++++++ .../orga/templates/orga/settings/form.html | 32 ++++--------------- .../orga/templates/orga/speaker/form.html | 12 +++++++ .../templates/orga/submission/content.html | 12 +++++++ src/pretalx/person/forms.py | 4 --- src/pretalx/static/common/scss/_forms.scss | 29 +++++++++++++++++ 9 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 src/pretalx/common/templates/common/widgets/image_input.html diff --git a/doc/changelog.rst b/doc/changelog.rst index 62b73997a..d15664376 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -3,6 +3,7 @@ Release Notes ============= +<<<<<<< HEAD - :feature:`schedule,1794` The iCal schedule export has been made private (available only to organisers) as the utility of importing a conference's entire schedule is limited, and people were frustrated that the iCal export did not reflect any applied schedule filters. - :bug:`schedule,1803` The QR code for schedule exporter links was not showing up when hovering on the QR code symbol. - :release:`2024.2.1 <2024-08-07>` @@ -19,6 +20,8 @@ Release Notes - :feature:`orga` Administrators (i.e. instance owners) can now search a list of all users, which includes their teams and permissions, and links to trigger account deletion and password resets. - :bug:`orga:review` Assigning reviewers could lead to incorrect assignments when browsers cached the form, but new reviewers were added to the team, shifting the overall order of input fields. - :feature:`cfp` Choice and multiple choice questions now use a drop-down with typeahead (search for options) when they have a lot of options. +======= +>>>>>>> 2f90329a3 (Show image preview to organisers) - :feature:`orga,1079` All images in forms in the organiser area now include a preview of the saved image, and open a lightbox instead of the image file when clicked. - :announcement:`admin` We now recommend that you use a virtualenv instead of the ``pip --user`` installation method, and have updated our install and upgrade documentation accordingly. - :bug:`orga` While organisers could reorder questions, and the order was saved and used in the frontend, the new order was not shown in the organiser backend. diff --git a/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html b/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html index 54df334f8..26fc2818e 100644 --- a/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html +++ b/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html @@ -21,6 +21,9 @@ {# do not compress #} + {% compress css %} + + {% endcompress %} {% endblock %} {% block content %} diff --git a/src/pretalx/common/forms/widgets.py b/src/pretalx/common/forms/widgets.py index 37de479fc..98d05e873 100644 --- a/src/pretalx/common/forms/widgets.py +++ b/src/pretalx/common/forms/widgets.py @@ -88,6 +88,8 @@ def get_context(self, name, value, attrs): class ImageInput(ClearableBasenameFileInput): + template_name = "common/widgets/image_input.html" + def get_context(self, name, value, attrs): attrs["accept"] = "image/*" return super().get_context(name, value, attrs) diff --git a/src/pretalx/common/templates/common/widgets/image_input.html b/src/pretalx/common/templates/common/widgets/image_input.html new file mode 100644 index 000000000..257cd038e --- /dev/null +++ b/src/pretalx/common/templates/common/widgets/image_input.html @@ -0,0 +1,13 @@ +{% if widget.value and widget.value.url %} +
+ + + +
+
+{% endif %} +{% if widget.is_initial %}{{ widget.initial_text }}: {{ widget.value }}{% if not widget.required %} + + {% endif %}
+ {{ widget.input_text }}:{% endif %} + diff --git a/src/pretalx/orga/templates/orga/settings/form.html b/src/pretalx/orga/templates/orga/settings/form.html index 634b6bb2f..f339de501 100644 --- a/src/pretalx/orga/templates/orga/settings/form.html +++ b/src/pretalx/orga/templates/orga/settings/form.html @@ -7,6 +7,9 @@ {% block stylesheets %} + {% compress css %} + + {% endcompress %} {% endblock %} {% block scripts %} @@ -17,6 +20,7 @@ + {% endcompress %} {% endblock %} @@ -69,32 +73,8 @@

{% translate "Settings" %}

{% bootstrap_field form.custom_css_text layout='event' %}
-
- -
- {% if request.event.logo %} - {% translate - {% endif %} -
- {% bootstrap_field form.logo layout="inline" %} -
-
-
- -
- {% if request.event.header_image %} - {% translate - {% endif %} -
- {% bootstrap_field form.header_image layout="inline" %} -
-
+ {% bootstrap_field form.logo layout="event" %} + {% bootstrap_field form.header_image layout="event" %}
{% bootstrap_field form.header_pattern layout='event' %}
diff --git a/src/pretalx/orga/templates/orga/speaker/form.html b/src/pretalx/orga/templates/orga/speaker/form.html index eba46d0d6..0c45707e1 100644 --- a/src/pretalx/orga/templates/orga/speaker/form.html +++ b/src/pretalx/orga/templates/orga/speaker/form.html @@ -5,6 +5,18 @@ {% load rules %} {% load static %} +{% block stylesheets %} + {% compress css %} + + {% endcompress %} +{% endblock %} + +{% block scripts %} + {% compress js %} + + {% endcompress %} +{% endblock %} + {% block title %}{{ form.instance.user.get_display_name }} :: {{ request.event.name }}{% endblock %} {% block content %} {% if form.biography %} diff --git a/src/pretalx/orga/templates/orga/submission/content.html b/src/pretalx/orga/templates/orga/submission/content.html index 8efd257cb..73598a2e5 100644 --- a/src/pretalx/orga/templates/orga/submission/content.html +++ b/src/pretalx/orga/templates/orga/submission/content.html @@ -7,6 +7,18 @@ {% load static %} {% load rules %} +{% block stylesheets %} + {% compress css %} + + {% endcompress %} +{% endblock %} + +{% block scripts %} + {% compress js %} + + {% endcompress %} +{% endblock %} + {% block submission_content %} {% has_perm 'orga.send_mails' request.user request.event as can_send_mails %} {% has_perm 'orga.view_all_reviews' request.user request.event as can_view_all_reviews %} diff --git a/src/pretalx/person/forms.py b/src/pretalx/person/forms.py index fdd74b0e2..6fd9373ce 100644 --- a/src/pretalx/person/forms.py +++ b/src/pretalx/person/forms.py @@ -10,7 +10,6 @@ from pretalx.cfp.forms.cfp import CfPFormMixin from pretalx.common.forms.fields import ( - ImageField, PasswordConfirmationField, PasswordField, SizeFileField, @@ -278,9 +277,6 @@ class Meta: model = SpeakerProfile fields = ("biography",) public_fields = ["name", "biography", "avatar"] - field_classes = { - "avatar": ImageField, - } widgets = { "biography": MarkdownWidget, "avatar_source": MarkdownWidget, diff --git a/src/pretalx/static/common/scss/_forms.scss b/src/pretalx/static/common/scss/_forms.scss index b0d3a3a35..83811614b 100644 --- a/src/pretalx/static/common/scss/_forms.scss +++ b/src/pretalx/static/common/scss/_forms.scss @@ -301,6 +301,35 @@ table .action-column { textarea { form-sizing: auto; } +.form-image-preview { + border: 1px solid $gray-light; + max-width: 180px; + background-size: 21px 21px; + background-position: + 0 0, + 10px 10px; + background-image: linear-gradient( + 45deg, + #efefef 25%, + rgba(239, 239, 239, 0) 25%, + rgba(239, 239, 239, 0) 75%, + #efefef 75%, + #efefef + ), + linear-gradient( + 45deg, + #efefef 25%, + rgba(239, 239, 239, 0) 25%, + rgba(239, 239, 239, 0) 75%, + #efefef 75%, + #efefef + ); + box-shadow: 0 0 6px 1px rgba(0, 0, 0, 0.1); + img { + max-width: 100%; + height: auto; + } +} @include media-breakpoint-down(md) { .submit-group { flex-direction: column; From 3e8ddca875e980e0a9cb44c64ad8fab243c2468e Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Wed, 13 Mar 2024 13:41:54 +0100 Subject: [PATCH 002/195] Use lightbox on speaker avatars --- .../common/templates/common/avatar.html | 25 ++++++++----- src/pretalx/static/cfp/js/profile.js | 37 +++++++++++-------- src/pretalx/static/common/scss/_avatar.scss | 25 +------------ 3 files changed, 38 insertions(+), 49 deletions(-) diff --git a/src/pretalx/common/templates/common/avatar.html b/src/pretalx/common/templates/common/avatar.html index 20552cc70..83a6f4368 100644 --- a/src/pretalx/common/templates/common/avatar.html +++ b/src/pretalx/common/templates/common/avatar.html @@ -1,5 +1,7 @@ {% load bootstrap4 %} {% load i18n %} +{% load static %} +{% load compress %}
- {% translate - {% bootstrap_field form.get_gravatar layout='event-inline' %} +
+ + {% translate + +
diff --git a/src/pretalx/static/cfp/js/profile.js b/src/pretalx/static/cfp/js/profile.js index bce29f7ae..27fbdce2f 100644 --- a/src/pretalx/static/cfp/js/profile.js +++ b/src/pretalx/static/cfp/js/profile.js @@ -1,4 +1,15 @@ +function setImage(url) { + const image = document.querySelector('.avatar-form img'); + const imageWrapper = document.querySelector('.avatar-form .form-image-preview'); + const imageLink = imageWrapper.querySelector('a'); + image.src = url; + imageLink.href = url; + imageLink.dataset.lightbox = url; + imageWrapper.classList.remove('d-none'); +} + $(function () { + const $imageLink = $(".avatar-form .form-image-preview a"); const $image = $(".avatar-form img"); const $fileInput = $(".avatar-form .avatar-upload input[type=file]"); const $resetCheckbox = $(".avatar-form .avatar-upload input[type=checkbox]"); @@ -13,24 +24,20 @@ $(function () { let files = $fileInput.prop('files'); if (files) { const reader = new FileReader(); - reader.onload = function (e) { - $image.attr('src', e.target.result); - $image.removeClass('d-none'); - }; + reader.onload = function (e) { setImage(e.target.result); }; reader.readAsDataURL(files[0]); $resetCheckbox.prop('checked', false); } else if ($image.data('avatar')) { - $image.attr('src', $image.data('avatar')); + setImage($image.data('avatar')); $resetCheckbox.prop('checked', false); } else { - $image.addClass('d-none'); + $imageWrapper.addClass('d-none'); } } else if ($image.data('avatar')) { - $image.attr('src', $image.data('avatar')); - $image.removeClass('d-none'); + setImage($image.data('avatar')); $resetCheckbox.prop('checked', false); } else { - $image.addClass('d-none'); + $imageWrapper.addClass('d-none'); } }); @@ -42,11 +49,10 @@ $(function () { setImage("https://www.gravatar.com/avatar/" + $image.data('gravatar') + '?s=512'); $resetCheckbox.prop('checked', true); } else if ($image.data('avatar')) { - $image.attr('src', $image.data('avatar')); - $image.removeClass('d-none'); + setImage($image.data('avatar')); $resetCheckbox.prop('checked', false); } else { - $image.addClass('d-none'); + $imageWrapper.addClass('d-none'); } }); @@ -54,12 +60,11 @@ $(function () { let isResetSelected = $resetCheckbox.prop('checked'); if (isResetSelected) { $fileInput.val(''); - $image.addClass('d-none'); + $imageWrapper.addClass('d-none'); } else if ($image.data('avatar')) { - $image.attr('src', $image.data('avatar')); - $image.removeClass('d-none'); + setImage($image.data('avatar')); } else { - $image.addClass('d-none'); + $imageWrapper.addClass('d-none'); } }) }); diff --git a/src/pretalx/static/common/scss/_avatar.scss b/src/pretalx/static/common/scss/_avatar.scss index f0e3f0a17..8d8f096a6 100644 --- a/src/pretalx/static/common/scss/_avatar.scss +++ b/src/pretalx/static/common/scss/_avatar.scss @@ -2,29 +2,8 @@ display: flex; align-items: flex-start; - img.avatar { - width: 100px; - height: auto; - } - - .avatar-form-fields { - display: flex; - flex-direction: column; - - .bootstrap4-multi-input, - .bootstrap4-multi-input > .col-12 { - margin: 0; - padding: 0; - } - - .form-group { - display: flex; - flex-direction: column; - } - - .user-avatar-display .form-group { - margin-bottom: 0; - } + .form-image-preview { + margin-left: 6px; } .avatar-upload .form-group { From a9748095b073cf33b17bb3e49553e180342f094d Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Wed, 13 Mar 2024 14:41:25 +0100 Subject: [PATCH 003/195] Fix docs links --- doc/_templates/index.html | 2 +- doc/changelog.rst | 2 +- doc/developer/plugins/index.rst | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/_templates/index.html b/doc/_templates/index.html index aaf9208a8..3c5e042fd 100644 --- a/doc/_templates/index.html +++ b/doc/_templates/index.html @@ -101,7 +101,7 @@

Project information

If you’d like to contribute to pretalx, you are most welcome! We have written a little about how to get started - here. + here.

diff --git a/doc/changelog.rst b/doc/changelog.rst index d15664376..f32cfb82d 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -637,7 +637,7 @@ Release Notes - :bug:`-` There was no possibility to reset a user’s API token. - :bug:`-` If an organiser changed a speaker’s email address, they could assign an address already in use in the pretalx instance, resulting in buggy behaviour all around. - :release:`0.5.0 <2018-03-07>` -- :feature:`-` pretalx now features a Plugin API, allowing to install custom plugins. Plugins can add their own exporters, and hook into plugin hooks. You can enable or disable plugins per event. You can find the plugin developer documentation: https://docs.pretalx.org/en/latest/developer/plugins/index.html +- :feature:`-` pretalx now features a Plugin API, allowing to install custom plugins. Plugins can add their own exporters, and hook into plugin hooks. You can enable or disable plugins per event. You can find the plugin developer documentation :ref:`here`. - :feature:`340` Organisers can now decide if reviewers should have to submit a score or a text with their review. - :feature:`93` Organisers can provide room-based information for speakers, and send it automatically in the emails about talk scheduling. - :feature:`318` The list of submissions is now better searchable. diff --git a/doc/developer/plugins/index.rst b/doc/developer/plugins/index.rst index 932a99c9d..4122d71b3 100644 --- a/doc/developer/plugins/index.rst +++ b/doc/developer/plugins/index.rst @@ -1,3 +1,5 @@ +.. _plugin-inde: + Plugin development ================== From 4180d2066b3bc1462426eaeed5f67fe832ccea60 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Wed, 13 Mar 2024 15:24:39 +0100 Subject: [PATCH 004/195] Set compress verbosity to 0 --- src/pretalx/common/management/commands/rebuild.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pretalx/common/management/commands/rebuild.py b/src/pretalx/common/management/commands/rebuild.py index f59355962..6f981e6fd 100644 --- a/src/pretalx/common/management/commands/rebuild.py +++ b/src/pretalx/common/management/commands/rebuild.py @@ -52,7 +52,9 @@ def handle(self, *args, **options): if options["npm_install"] or not (frontend_dir / "node_modules").exists(): subprocess.check_call(["npm", "ci"], cwd=frontend_dir) subprocess.check_call(["npm", "run", "build"], cwd=frontend_dir, env=env) - call_command("compress", verbosity=silent) + + # We're setting the verbosity to 0 when calling compress on account of https://github.com/django-compressor/django-compressor/issues/881 + call_command("compress", verbosity=0) # This fails if we don't have db access, which is fine with suppress(Exception): From dde55c596f18ad7f1803a0c7682be1e49f543dcb Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 15 Mar 2024 11:56:37 +0100 Subject: [PATCH 005/195] Fix bug in review export --- src/pretalx/orga/forms/review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pretalx/orga/forms/review.py b/src/pretalx/orga/forms/review.py index 9919ac1f5..eaf49d073 100644 --- a/src/pretalx/orga/forms/review.py +++ b/src/pretalx/orga/forms/review.py @@ -330,7 +330,7 @@ def _build_score_fields(self): def get_additional_data(self, obj): return { - sc.name: getattr(obj.scores.filter(category=sc).first(), "value", None) + str(sc.name): getattr(obj.scores.filter(category=sc).first(), "value", None) for sc in self.score_categories } From 9c0c99f083525ce17e46ed0f81a197a287df34bf Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 19 Mar 2024 11:35:22 +0100 Subject: [PATCH 006/195] Add typeahead to questions with many options --- doc/changelog.rst | 4 + .../cfp/templates/cfp/event/user_profile.html | 2 + .../cfp/event/user_submission_edit.html | 3 + src/pretalx/common/mixins/forms.py | 12 +- .../static/common/scss/_variables.scss | 4 + src/pretalx/static/orga/scss/_select.scss | 114 ------------------ src/pretalx/static/orga/scss/main.scss | 2 +- 7 files changed, 24 insertions(+), 117 deletions(-) delete mode 100644 src/pretalx/static/orga/scss/_select.scss diff --git a/doc/changelog.rst b/doc/changelog.rst index f32cfb82d..589bb804f 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -3,6 +3,7 @@ Release Notes ============= +<<<<<<< HEAD <<<<<<< HEAD - :feature:`schedule,1794` The iCal schedule export has been made private (available only to organisers) as the utility of importing a conference's entire schedule is limited, and people were frustrated that the iCal export did not reflect any applied schedule filters. - :bug:`schedule,1803` The QR code for schedule exporter links was not showing up when hovering on the QR code symbol. @@ -22,6 +23,9 @@ Release Notes - :feature:`cfp` Choice and multiple choice questions now use a drop-down with typeahead (search for options) when they have a lot of options. ======= >>>>>>> 2f90329a3 (Show image preview to organisers) +======= +- :feature:`cfp` Choice and multiple choice questions now use a drop-down with typeahead (search for options) when they have a lot of options. +>>>>>>> c326d2761 (Add typeahead to questions with many options) - :feature:`orga,1079` All images in forms in the organiser area now include a preview of the saved image, and open a lightbox instead of the image file when clicked. - :announcement:`admin` We now recommend that you use a virtualenv instead of the ``pip --user`` installation method, and have updated our install and upgrade documentation accordingly. - :bug:`orga` While organisers could reorder questions, and the order was saved and used in the frontend, the new order was not shown in the organiser backend. diff --git a/src/pretalx/cfp/templates/cfp/event/user_profile.html b/src/pretalx/cfp/templates/cfp/event/user_profile.html index 803e25978..5a73579a9 100644 --- a/src/pretalx/cfp/templates/cfp/event/user_profile.html +++ b/src/pretalx/cfp/templates/cfp/event/user_profile.html @@ -16,6 +16,8 @@ {% compress js %} + + {% endcompress %} {% if profile_form.biography %} {# do not compress #} diff --git a/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html b/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html index 26fc2818e..0226991b0 100644 --- a/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html +++ b/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html @@ -17,12 +17,15 @@ + {% endcompress %} {# do not compress #} {% compress css %} + + {% endcompress %} {% endblock %} diff --git a/src/pretalx/common/mixins/forms.py b/src/pretalx/common/mixins/forms.py index 33d7aae7f..eea2c2966 100644 --- a/src/pretalx/common/mixins/forms.py +++ b/src/pretalx/common/mixins/forms.py @@ -251,7 +251,11 @@ def get_field(self, *, question, initial, initial_object, readonly): ), disabled=read_only, help_text=help_text, - widget=forms.RadioSelect if len(choices) < 4 else None, + widget=( + forms.RadioSelect + if len(choices) < 4 + else forms.Select(attrs={"class": "select2"}) + ), ) field.original_help_text = original_help_text field.widget.attrs["placeholder"] = "" # XSS @@ -261,7 +265,11 @@ def get_field(self, *, question, initial, initial_object, readonly): queryset=question.options.all(), label=question.question, required=question.required, - widget=forms.CheckboxSelectMultiple, + widget=( + forms.CheckboxSelectMultiple + if len(choices) < 8 + else forms.SelectMultiple(attrs={"class": "select2"}) + ), initial=( initial_object.options.all() if initial_object diff --git a/src/pretalx/static/common/scss/_variables.scss b/src/pretalx/static/common/scss/_variables.scss index 774cb6bf9..1644467d2 100644 --- a/src/pretalx/static/common/scss/_variables.scss +++ b/src/pretalx/static/common/scss/_variables.scss @@ -45,3 +45,7 @@ $theme-colors: ( dark: $gray-800, ); $enable-validation-icons: false; + +:root { + --color-danger: #b23e65; +} diff --git a/src/pretalx/static/orga/scss/_select.scss b/src/pretalx/static/orga/scss/_select.scss deleted file mode 100644 index e12e83b47..000000000 --- a/src/pretalx/static/orga/scss/_select.scss +++ /dev/null @@ -1,114 +0,0 @@ -.select2-textbox { - .form-group .select2-container--default .select2-selection--multiple, - .form-group .select2-container--default .select2-selection { - height: auto; - .select2-selection__rendered { - overflow: hidden; - height: auto; - display: flex; - flex-wrap: wrap; - .select2-selection__choice { - height: 28px; - } - } - .select2-search.select2-search--inline { - width: 20px; - } - } -} -.form-group, -.score-group { - .select2-container--default { - width: 100% !important; - .select2-selection--multiple, - .select2-selection { - border: 1px solid #ced4da; - height: 38px; - width: 100% !important; - overflow-y: hidden; - } - .select2-selection--single { - .select2-selection__arrow { - height: 36px; - } - .select2-selection__rendered { - line-height: 36px; - color: #495057; - } - } - .select2-selection--multiple { - .select2-selection__choice { - background-color: white; - } - .select2-selection__choice__remove { - border-right: none; - padding: 0 7px; - } - } - &.select2-container--focus { - .select2-selection, - .select2-selection--multiple { - box-shadow: 0 0 0 1px rgba(58, 165, 124, 0.25); - border-color: #89d6b8; - } - } - .select2-selection--clearable { - button.select2-selection__clear { - margin-top: 4px; - color: $brand-danger; - } - } - } - .select2-container { - .select2-search--inline { - width: calc(100% - 20px); - .select2-search__field { - height: auto; - margin-left: 12px; - font-size: 1rem; - font-family: Muli; - } - } - .select2-selection--multiple { - display: flex; - - .select2-selection__rendered { - display: inline-flex; - margin-bottom: 0; - max-width: 100%; - min-height: 32px; - - &::after { - content: ""; - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - border-top: 5px solid #6c757d; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - } - } - } - .select2-selection--single .select2-selection__rendered { - padding-left: 18px; - } - } - .selection--multiple .select2-selection__rendered { - display: inline; - vertical-align: top; - } -} -.form-group.row .select2 textarea::placeholder { - color: white; -} -.select2-search input[type="search"] { - border-radius: 4px; - &:focus, - &:focus-visible { - /* on search fields, focus-visible applies when on other fields focus applies, so we copy :focus rules here */ - box-shadow: 0 0 0 1px rgba(58, 165, 124, 0.25); - border-color: #89d6b8; - outline: 0; - } -} diff --git a/src/pretalx/static/orga/scss/main.scss b/src/pretalx/static/orga/scss/main.scss index 22d73402a..49bca150f 100644 --- a/src/pretalx/static/orga/scss/main.scss +++ b/src/pretalx/static/orga/scss/main.scss @@ -12,9 +12,9 @@ @import "../../vendored/forkawesome/scss/fork-awesome"; @import "../../vendored/datetimepicker/_bootstrap-datetimepicker"; @import "../../common/scss/pretalx"; +@import "../../common/scss/select2"; @import "_layout"; @import "_flow"; -@import "_select"; @import "_rtl"; h3 { From 378a8553879d033e7e0801440c92f35ad4b05560 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 19 Mar 2024 11:47:14 +0100 Subject: [PATCH 007/195] Fix bug in questions form --- src/pretalx/common/mixins/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pretalx/common/mixins/forms.py b/src/pretalx/common/mixins/forms.py index eea2c2966..c9838a294 100644 --- a/src/pretalx/common/mixins/forms.py +++ b/src/pretalx/common/mixins/forms.py @@ -261,8 +261,9 @@ def get_field(self, *, question, initial, initial_object, readonly): field.widget.attrs["placeholder"] = "" # XSS return field if question.variant == QuestionVariant.MULTIPLE: + choices = question.options.all() field = forms.ModelMultipleChoiceField( - queryset=question.options.all(), + queryset=choices, label=question.question, required=question.required, widget=( From a60be1af79b53d52310a30c8de8db0b234987d8a Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 19 Mar 2024 12:11:34 +0100 Subject: [PATCH 008/195] Bump djangorestframework --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8b7cf74f5..ff373d13d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ "django-libsass~=0.8", "django-scopes~=2.0.0", "django-pdb~=0.6.2", - "djangorestframework~=3.14.0", + "djangorestframework~=3.15.0", "libsass~=0.23.0", "Markdown~=3.5.0", # https://python-markdown.github.io/change_log/ # We can upgrade markdown again once django-bootstrap4 upgrades or once we drop Python 3.6 and 3.7 From ee28e4d033bed00f6b5e6c25f1b03ea5c60dd73c Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 19 Mar 2024 12:12:55 +0100 Subject: [PATCH 009/195] Bump Markdown --- pyproject.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ff373d13d..24c2d1f32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,10 +48,7 @@ dependencies = [ "django-pdb~=0.6.2", "djangorestframework~=3.15.0", "libsass~=0.23.0", - "Markdown~=3.5.0", # https://python-markdown.github.io/change_log/ - # We can upgrade markdown again once django-bootstrap4 upgrades or once we drop Python 3.6 and 3.7 - # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3. - # see also https://github.com/zostera/django-bootstrap4/issues/380 + "Markdown~=3.6.0", "Pillow~=10.2.0", "publicsuffixlist~=0.10.0", "python-dateutil~=2.9.0", From c9f2a691813cbc41a56c11a706babc437425ac2c Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 26 Mar 2024 18:28:43 +0100 Subject: [PATCH 010/195] Clarify reverse proxy configuration --- doc/administrator/installation.rst | 66 +++++------------------------- 1 file changed, 11 insertions(+), 55 deletions(-) diff --git a/doc/administrator/installation.rst b/doc/administrator/installation.rst index 51aff5b39..5c25dcc44 100644 --- a/doc/administrator/installation.rst +++ b/doc/administrator/installation.rst @@ -211,73 +211,29 @@ You can now run the following commands to enable and start the services:: # systemctl enable pretalx-web pretalx-worker # systemctl start pretalx-web pretalx-worker -Step 7: SSL ------------ - -.. highlight:: nginx +Step 7: Reverse proxy +--------------------- You’ll need to set up an HTTP reverse proxy to handle HTTPS connections. It doesn’t particularly matter which one you use, as long as you make sure to use `strong encryption settings`_. Your proxy should -* serve all requests exclusively over HTTPS -* set the ``X-Forwarded-For`` and ``X-Forwarded-Proto`` headers -* set the ``Host`` header +* serve all requests exclusively over HTTPS, +* follow established security practices regarding protocols and ciphers. +* optionally set best-practice headers like ``Referrer-Policy`` and + ``X-Content-Type-Options``, +* set the ``X-Forwarded-For`` and ``X-Forwarded-Proto`` headers, +* set the ``Host`` header, * serve all requests for the ``/static/`` and ``/media/`` paths from the directories you set up in the previous step, without permitting directory - listings or traversal -* pass requests to the gunicorn server you set up in the previous step - -The following snippet is an example on how to configure an nginx proxy for pretalx:: - - server { - listen 80 default_server; - listen [::]:80 ipv6only=on default_server; - server_name pretalx.mydomain.com; - } - server { - listen 443 default_server; - listen [::]:443 ipv6only=on default_server; - server_name pretalx.mydomain.com; - - ssl on; - ssl_certificate /path/to/cert.chain.pem; - ssl_certificate_key /path/to/key.pem; - - gzip off; - add_header Referrer-Policy same-origin; - add_header X-Content-Type-Options nosniff; - - location / { - proxy_pass http://localhost:8345/; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto https; - proxy_set_header Host $http_host; - } - - location /media/ { - gzip on; - alias /var/pretalx/data/media/; - add_header Content-Disposition 'attachment; filename="$1"'; - expires 7d; - access_log off; - } - - location /static/ { - gzip on; - alias /path/to/static.dist/; - access_log off; - expires 365d; - add_header Cache-Control "public"; - } - } + listings or traversal. Files in the ``/media/`` directory should be served + as attachments. You can use fairly aggressive cache settings for these URLs, and +* pass all other requests to the gunicorn server you set up in the previous step. Step 8: Check the installation ------------------------------- -.. highlight:: console - You can make sure the web interface is up and look for any issues with:: # journalctl -u pretalx-web From 2074b3369ba8ab0a9054a10c369d2c0d9e763858 Mon Sep 17 00:00:00 2001 From: pretalx-translations Date: Tue, 2 Apr 2024 14:07:57 +0200 Subject: [PATCH 011/195] Translations update: Korean MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Han Lee | 이한결 --- src/pretalx/locale/ko/LC_MESSAGES/django.po | 343 ++++++++++++++++++-- 1 file changed, 318 insertions(+), 25 deletions(-) diff --git a/src/pretalx/locale/ko/LC_MESSAGES/django.po b/src/pretalx/locale/ko/LC_MESSAGES/django.po index ca85719c2..073679354 100644 --- a/src/pretalx/locale/ko/LC_MESSAGES/django.po +++ b/src/pretalx/locale/ko/LC_MESSAGES/django.po @@ -175,6 +175,9 @@ msgstr "발표자의 프로필 사진" msgid "Session" msgid_plural "Sessions" msgstr[0] "세션" +#: pretalx/agenda/templates/agenda/talk.html:37 +msgid "Favourite this session" +msgstr "즐겨찾기에 추가" #: pretalx/agenda/templates/agenda/talk.html:38 msgid "Favourite this session" @@ -475,9 +478,8 @@ msgstr "자세한 내용은 컨퍼런스 주최자에게 문의하시기 바랍 msgid "" "You, %(name)s, have been invited to be a speaker for the session “%(talk)s”. " "Do you accept the invitation?" -msgstr "" -"%(name)s님께서는 “%(talk)s” 발표의 발표자로 초대되셨습니다. 이 초대를 수락하" -"시겠습니까?" +msgstr "%(name)s님께서는 “%(talk)s” 발표의 발표자로 초대되셨습니다. 이 초대를 " +"수락하시겠습니까?" #: pretalx/cfp/templates/cfp/event/invitation.html:28 msgid "Abstract:" @@ -555,7 +557,30 @@ msgstr "" "제안서를 초안으로 저장하고 나중에 제출할 수 있습니다. 주최자는 제안서를 볼 수" "는 없지만, 다가오는 마감일에 대한 알림 이메일을 보낼 수 있습니다." -#: pretalx/cfp/templates/cfp/event/submission_questions.html:35 +#: pretalx/cfp/templates/cfp/event/submission_base.html:64 +#: pretalx/orga/templates/orga/cfp/access_code_delete.html:11 +#: pretalx/orga/templates/orga/cfp/question_delete.html:11 +#: pretalx/orga/templates/orga/cfp/submission_type_delete.html:11 +#: pretalx/orga/templates/orga/cfp/track_delete.html:11 +#: pretalx/orga/templates/orga/event/delete.html:12 +#: pretalx/orga/templates/orga/mails/confirm.html:12 +#: pretalx/orga/templates/orga/organiser/delete.html:14 +#: pretalx/orga/templates/orga/review/regenerate_decision_mails.html:16 +#: pretalx/orga/templates/orga/schedule/release.html:120 +#: pretalx/orga/templates/orga/settings/team_delete.html:15 +#: pretalx/orga/templates/orga/settings/team_resend.html:15 +#: pretalx/orga/templates/orga/settings/team_reset_password.html:15 +#: pretalx/orga/templates/orga/speaker/information_delete.html:12 +#: pretalx/orga/templates/orga/speaker/reset_password.html:15 +#: pretalx/orga/templates/orga/submission/apply_pending.html:22 +#: pretalx/orga/templates/orga/submission/apply_pending.html:37 +#: pretalx/orga/templates/orga/submission/review_delete.html:11 +#: pretalx/orga/templates/orga/submission/state_change.html:27 +#: pretalx/orga/templates/orga/submission/tag_delete.html:11 +msgid "Back" +msgstr "뒤로" + +#: pretalx/cfp/templates/cfp/event/submission_questions.html:28 msgid "… about your proposal:" msgstr "... 당신의 제안서에 대하여:" @@ -573,8 +598,8 @@ msgid "" "will be able to send you reminders about your pending proposal draft closer " "to the deadline." msgstr "" -"주최자는 초안이나 이메일 주소를 볼 수 없습니다. 마감일이 가까워지면 보류 중" -"인 제안서 초안에 대한 독촉(리마인더)을 보낼 수 있습니다." +"주최자는 초안이나 이메일 주소를 볼 수 없습니다. 마감일이 가까워지면 보류 " +"중인 제안서 초안에 대한 독촉(리마인더)을 보낼 수 있습니다." #: pretalx/cfp/templates/cfp/event/user_mails.html:4 #: pretalx/cfp/templates/cfp/event/user_mails.html:7 @@ -604,6 +629,31 @@ msgstr "" "이 데이터는 제안이 수락되면 공개적으로 표시됩니다. 검토자에게도 표시됩니다." #: pretalx/cfp/templates/cfp/event/user_profile.html:61 +#: pretalx/cfp/templates/cfp/event/user_profile.html:82 +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:185 +#: pretalx/common/phrases.py:44 +#: pretalx/orga/templates/orga/cfp/access_code_form.html:53 +#: pretalx/orga/templates/orga/includes/submit_row.html:11 +#: pretalx/orga/templates/orga/mails/outbox_form.html:109 +#: pretalx/orga/templates/orga/organiser/detail.html:25 +#: pretalx/orga/templates/orga/review/assignment.html:173 +#: pretalx/orga/templates/orga/review/bulk.html:107 +#: pretalx/orga/templates/orga/schedule/quick.html:18 +#: pretalx/orga/templates/orga/settings/form.html:112 +#: pretalx/orga/templates/orga/settings/mail.html:32 +#: pretalx/orga/templates/orga/settings/review.html:34 +#: pretalx/orga/templates/orga/settings/review.html:154 +#: pretalx/orga/templates/orga/settings/review.html:244 +#: pretalx/orga/templates/orga/settings/widget.html:26 +#: pretalx/orga/templates/orga/speaker/form.html:82 +#: pretalx/orga/templates/orga/submission/anonymise.html:43 +#: pretalx/orga/templates/orga/submission/content.html:164 +#: pretalx/orga/templates/orga/submission/review.html:215 +#: pretalx/orga/templates/orga/update.html:91 +msgid "Save" +msgstr "저장" + +#: pretalx/cfp/templates/cfp/event/user_profile.html:54 msgid "We have some questions" msgstr "몇 가지 질문이 있습니다" @@ -641,6 +691,44 @@ msgid "Delete my account" msgstr "내 계정 삭제" #: pretalx/cfp/templates/cfp/event/user_submission_confirm.html:13 +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:37 +#: pretalx/cfp/templates/cfp/event/user_submission_invitation.html:14 +#: pretalx/cfp/templates/cfp/event/user_submission_withdraw.html:9 +msgid "Current state of your proposal:" +msgstr "제안서의 현재 상태:" + +#: pretalx/cfp/templates/cfp/event/user_submission_confirm.html:16 +#: pretalx/cfp/templates/cfp/event/user_submission_discard.html:12 +#: pretalx/orga/templates/orga/cfp/submission_type_view.html:23 +#: pretalx/orga/templates/orga/review/dashboard.html:109 +#: pretalx/submission/models/submission.py:134 +msgid "Session type" +msgstr "세션 유형" + +#: pretalx/cfp/templates/cfp/event/user_submission_confirm.html:17 +#: pretalx/cfp/templates/cfp/event/user_submission_discard.html:13 +#: pretalx/orga/templates/orga/cfp/access_code_view.html:32 +#: pretalx/orga/templates/orga/cfp/text.html:101 +#: pretalx/orga/templates/orga/cfp/track_form.html:22 +#: pretalx/orga/templates/orga/cfp/track_view.html:23 +#: pretalx/orga/templates/orga/review/dashboard.html:101 +#: pretalx/orga/templates/orga/review/dashboard.html:217 +#: pretalx/orga/templates/orga/submission/review.html:65 +#: pretalx/submission/models/access_code.py:28 +#: pretalx/submission/models/submission.py:140 +msgid "Track" +msgstr "트랙" + +#: pretalx/cfp/templates/cfp/event/user_submission_confirm.html:19 +#: pretalx/cfp/templates/cfp/event/user_submission_discard.html:15 +#: pretalx/orga/templates/orga/cfp/text.html:157 +#: pretalx/orga/templates/orga/review/dashboard.html:105 +#: pretalx/orga/templates/orga/review/dashboard.html:218 +#: pretalx/submission/models/submission.py:194 +msgid "Duration" +msgstr "기간" + +#: pretalx/cfp/templates/cfp/event/user_submission_confirm.html:21 msgid "Congratulations on your acceptance!" msgstr "허가를 축하합니다!" @@ -663,9 +751,15 @@ msgstr "" "지가 있다는 데 동의하는 것입니다. 제안이 확정되면 제목, 초록, 설명 및 제공한 " "모든 업로드와 같은 제안 데이터는 공개적으로 사용할 수 있습니다." -#: pretalx/cfp/templates/cfp/event/user_submission_confirm.html:45 -#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:120 -#: pretalx/cfp/templates/cfp/event/user_submission_withdraw.html:19 +#: pretalx/cfp/templates/cfp/event/user_submission_confirm.html:49 +#: pretalx/cfp/templates/cfp/event/user_submission_discard.html:26 +#: pretalx/cfp/templates/cfp/event/user_submission_withdraw.html:27 +msgid "Go back" +msgstr "돌아가기" + +#: pretalx/cfp/templates/cfp/event/user_submission_confirm.html:53 +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:211 +#: pretalx/cfp/templates/cfp/event/user_submission_withdraw.html:23 msgid "Withdraw" msgstr "철회하기" @@ -921,15 +1015,9 @@ msgstr "" "이것은 제안서 초안입니다. 제출하거나 명시적으로 공유하지 않는 한 다른 사람에" "게 절대 공개되지 않습니다." -#: pretalx/cfp/templates/cfp/includes/user_submission_header.html:30 -#: pretalx/orga/templates/orga/cfp/access_code_view.html:33 -#: pretalx/orga/templates/orga/cfp/submission_type_form.html:19 -#: pretalx/orga/templates/orga/cfp/submission_type_view.html:23 -#: pretalx/orga/templates/orga/review/dashboard.html:109 -#: pretalx/orga/templates/orga/submission/review.html:59 -#: pretalx/orga/views/cfp.py:498 pretalx/submission/models/submission.py:136 -msgid "Session type" -msgstr "세션 유형" +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:49 +msgid "Confirm your attendance" +msgstr "수락 (참석을 확정합니다)" #: pretalx/cfp/templates/cfp/includes/user_submission_header.html:31 #: pretalx/orga/templates/orga/cfp/access_code_view.html:32 @@ -944,13 +1032,218 @@ msgstr "세션 유형" msgid "Track" msgstr "트랙" -#: pretalx/cfp/templates/cfp/includes/user_submission_header.html:33 -#: pretalx/orga/templates/orga/cfp/text.html:157 -#: pretalx/orga/templates/orga/review/dashboard.html:105 -#: pretalx/orga/templates/orga/review/dashboard.html:218 -#: pretalx/submission/models/submission.py:196 -msgid "Duration" -msgstr "기간" +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:56 +msgid "Attendees can leave feedback here after your session has taken place." +msgstr "참석자는 세션이 진행된 후 여기에서 피드백을 남길 수 있습니다." + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:65 +msgid "Speaker" +msgid_plural "Speakers" +msgstr[0] "발표자" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:67 +msgid "Submitter" +msgid_plural "Submitters" +msgstr[0] "제출자" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:92 +#: pretalx/orga/forms/schedule.py:120 +#: pretalx/orga/templates/orga/submission/content.html:80 +#: pretalx/orga/templates/orga/submission/review.html:117 +msgid "Resources" +msgstr "자료" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:96 +msgid "" +"Resources will be publicly visible. Please try to keep your uploads below " +"16MB." +msgstr "자료가 공개적으로 표시됩니다. 업로드 용량을 16MB 미만으로 유지하시기 " +"바랍니다." + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:148 +#: pretalx/orga/templates/orga/submission/content.html:131 +msgid "You can either provide a URL or upload a file." +msgstr "URL을 입력하거나 파일을 업로드할 수 있습니다." + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:149 +#: pretalx/orga/templates/orga/submission/content.html:132 +msgid "Max file size:" +msgstr "최대 파일 크기:" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:164 +#: pretalx/orga/templates/orga/submission/content.html:140 +msgid "Add another resource" +msgstr "다른 자료 추가" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:176 +msgid "Save draft" +msgstr "초안 저장" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:179 +msgid "Submit proposal" +msgstr "제안서 제출" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:192 +msgid "Share proposal" +msgstr "제안서 공유" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:194 +msgid "" +"If you need a review from a colleague or a friend here’s a link that you can " +"send out for viewing your proposal:" +msgstr "(동료나 친구의 검토가 필요한 경우) 제안서를 확인할 수 있는 공유용 링크:" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:200 +#: pretalx/cfp/templates/cfp/event/user_submission_withdraw.html:14 +msgid "Withdraw proposal" +msgstr "발표 제안 철회(취소)" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:202 +msgid "" +"You can withdraw your proposal from the selection process here. You cannot " +"undo this - if you are just uncertain if you can or should hold your " +"session, please contact the organiser instead." +msgstr "" +"여기에서 제안서를 철회(취소)할 수 있습니다. 이 작업은 되돌릴 수 없습니다. " +"만약 본인 제안에 확신이 없는 경우, 운영진에게 문의하는 것을 고려해 주세요." + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:218 +msgid "" +"You can discard your draft proposal here. You cannot undo this - if you are " +"just uncertain if you can or should submit your proposal, please contact the " +"organiser instead." +msgstr "" +"여기에서 제안서 초안을 삭제할 수 있습니다. 이 작업은 되돌릴 수 없습니다. " +"만약 본인 제안에 확신이 없는 경우, 운영진에게 문의하는 것을 고려해 주세요." + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:227 +#: pretalx/orga/templates/orga/mails/outbox_form.html:106 +msgid "Discard" +msgstr "삭제" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:232 +msgid "Cancel proposal" +msgstr "발표 취소" + +#: pretalx/cfp/templates/cfp/event/user_submission_edit.html:234 +msgid "" +"As your proposal has been accepted already, please contact the event’s " +"organising team to cancel it. The best way to reach out would be an answer " +"to your acceptance mail." +msgstr "제안서가 이미 수락 되었습니다. 취소하려면 이벤트 주최 팀에 문의하시기 " +"바랍니다." + +#: pretalx/cfp/templates/cfp/event/user_submission_invitation.html:18 +msgid "" +"Invite another speaker to your proposal here. Instead of letting us send an " +"email, (which might get caught by spam filters) you can also give them this " +"link:" +msgstr "여기에서 당신의 발표 제안에 다른 발표자를 초대하세요. 메일을 보내는(스팸 " +"처리될 수 있음) 대신 다음 링크를 제공해도 됩니다:" + +#: pretalx/cfp/templates/cfp/event/user_submission_invitation.html:31 +#: pretalx/common/phrases.py:45 +msgid "Cancel" +msgstr "취소" + +#: pretalx/cfp/templates/cfp/event/user_submission_invitation.html:34 +#: pretalx/common/phrases.py:43 +#: pretalx/orga/templates/orga/cfp/access_code_send.html:26 +#: pretalx/orga/templates/orga/cfp/question_remind.html:22 +#: pretalx/orga/templates/orga/mails/compose_reviewer_mail_form.html:24 +#: pretalx/orga/templates/orga/mails/outbox_list.html:89 +#: pretalx/orga/templates/orga/mails/send_draft_reminders.html:21 +msgid "Send" +msgstr "보내기" + +#: pretalx/cfp/templates/cfp/event/user_submission_withdraw.html:16 +msgid "Do you really want to withdraw your proposal?" +msgstr "정말로 발표 제안을 철회하시겠습니까?" + +#: pretalx/cfp/templates/cfp/event/user_submission_withdraw.html:29 +msgid "You will not be able to revert this action." +msgstr "이 작업은 되돌릴 수 없습니다." + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:9 +#: pretalx/cfp/templates/cfp/event/user_submissions.html:67 +msgid "Your proposals" +msgstr "당신의 (발표) 제안" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:13 +msgid "Important Information" +msgstr "중요 정보" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:25 +msgid "Your drafts" +msgstr "당신의 초안" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:30 +#: pretalx/cfp/templates/cfp/event/user_submissions.html:73 +#: pretalx/orga/templates/orga/cfp/text.html:83 +#: pretalx/orga/templates/orga/review/bulk.html:67 +#: pretalx/orga/templates/orga/review/dashboard.html:212 +#: pretalx/orga/templates/orga/speaker/information_list.html:29 +#: pretalx/orga/templates/orga/submission/list.html:91 +#: pretalx/orga/templates/orga/submission/review.html:48 +msgid "Title" +msgstr "제목" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:48 +#: pretalx/cfp/templates/cfp/event/user_submissions.html:105 +msgid "Copy code for review" +msgstr "리뷰를 위한 코드 복사" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:55 +msgid "Edit draft" +msgstr "초안 수정하기" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:57 +msgid "Open draft" +msgstr "초안 열기" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:74 +#: pretalx/orga/templates/orga/review/dashboard.html:119 +#: pretalx/orga/templates/orga/review/dashboard.html:221 +#: pretalx/orga/templates/orga/submission/list.html:102 +msgid "State" +msgstr "상태" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:112 +msgid "Edit proposal" +msgstr "제안서 수정하기" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:114 +msgid "Open proposal" +msgstr "발표 제안서 열기" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:126 +#: pretalx/orga/templates/orga/base.html:273 +#: pretalx/orga/templates/orga/submission/base.html:64 +#: pretalx/submission/models/feedback.py:36 +msgid "Feedback" +msgstr "피드백" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:139 +msgid "Create a new proposal" +msgstr "새로운 발표 제안 만들기" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:145 +msgid "It seems like you haven’t submitted anything to this event yet." +msgstr "" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:148 +msgid "If you did, maybe you used a different account? Check your emails!" +msgstr "" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:152 +msgid "" +"If you did not, why not go ahead and create a proposal now? We’d love to " +"hear from you!" +msgstr "" + +#: pretalx/cfp/templates/cfp/event/user_submissions.html:157 +msgid "Submit something now!" +msgstr "" #: pretalx/cfp/templates/cfp/index.html:4 #: pretalx/cfp/templates/cfp/index.html:8 From 8bbecad749089c5d7db06710a91b0f67d8ffe32f Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 2 Apr 2024 17:36:14 +0200 Subject: [PATCH 012/195] Bump django-filter --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 24c2d1f32..b0367958d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "django-context-decorator", "django-countries~=7.0", "django-csp~=3.8.0", - "django-filter==24.1", + "django-filter==24.2", "django-formset-js-improved==0.5.0.3", "django-formtools~=2.5.1", "django-hierarkey~=1.1.0", From e6b721c4efcc9ebe71d22739c16793ba49c895aa Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 2 Apr 2024 17:36:33 +0200 Subject: [PATCH 013/195] Bump cssutils --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b0367958d..f16635d0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "celery~=5.3.0", "css_inline~=0.13.0", "csscompressor~=0.9.0", - "cssutils~=2.9.0", + "cssutils~=2.10.0", "defusedcsv~=2.0.0", "defusedxml~=0.7.0", "Django[argon2]~=4.2.0", From 60faa9f298c7f5a7f0aa91b83ac1ba03f4d9e1c1 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 2 Apr 2024 17:37:25 +0200 Subject: [PATCH 014/195] Bump css-inline --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f16635d0e..66407409e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "beautifulsoup4[lxml]~=4.12.0", "bleach~=6.1.0", "celery~=5.3.0", - "css_inline~=0.13.0", + "css_inline~=0.14.0", "csscompressor~=0.9.0", "cssutils~=2.10.0", "defusedcsv~=2.0.0", From 9d25b8fbd6ca10cf3da283ba80a4384fb16992a4 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 2 Apr 2024 18:06:42 +0200 Subject: [PATCH 015/195] Show review tab to reviewers even when reviews are hidden --- src/pretalx/orga/templates/orga/submission/base.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pretalx/orga/templates/orga/submission/base.html b/src/pretalx/orga/templates/orga/submission/base.html index 9cfcdda83..4762dd588 100644 --- a/src/pretalx/orga/templates/orga/submission/base.html +++ b/src/pretalx/orga/templates/orga/submission/base.html @@ -15,6 +15,7 @@ {% if submission %} {% has_perm 'submission.edit_submission' request.user submission as can_edit_submission %} {% has_perm 'orga.view_reviews' request.user request.event as can_view_reviews %} + {% has_perm 'submission.review_submission' request.user submission as can_review %} {% has_perm 'orga.view_speakers' request.user request.event as can_view_speakers %} {% has_perm 'orga.send_mails' request.user request.event as can_send_mails %}

@@ -65,7 +66,7 @@

{% endif %} - {% if can_view_reviews %} + {% if can_view_reviews or can_review %} + {% endblocktranslate %} {% translate "See all unconfirmed sessions." %} {% endif %} {% if warnings.unscheduled %}
  • {% blocktranslate trimmed count count=warnings.unscheduled %} diff --git a/src/pretalx/orga/views/submission.py b/src/pretalx/orga/views/submission.py index 811d18cc0..2ef4dd490 100644 --- a/src/pretalx/orga/views/submission.py +++ b/src/pretalx/orga/views/submission.py @@ -225,6 +225,12 @@ def do(self, force=False, pending=False): if pending: self.object.pending_state = self._target self.object.save() + if self.object.pending_state in [ + SubmissionStates.ACCEPTED, + SubmissionStates.CONFIRMED, + ]: + # allow configureability of pending accepted/confirmed talks + self.object.update_talk_slots() else: method = getattr(self.object, SubmissionStates.method_names[self._target]) method(person=self.request.user, force=force, orga=True) diff --git a/src/pretalx/submission/models/submission.py b/src/pretalx/submission/models/submission.py index a3901ec12..3f5f6f143 100644 --- a/src/pretalx/submission/models/submission.py +++ b/src/pretalx/submission/models/submission.py @@ -430,7 +430,10 @@ def update_talk_slots(self): """ from pretalx.schedule.models import TalkSlot - if self.state not in [SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED]: + # scheduling is only allowed (and therefore slots needed) for accepted and confirmed talks, or those pending counterparts + scheduling_allowed = self.state in [SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED] or self.pending_state in [SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED] + + if not scheduling_allowed: TalkSlot.objects.filter( submission=self, schedule=self.event.wip_schedule ).delete() From 811e70ea6b8b292d303579c230961c389b6f371d Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Mon, 22 Apr 2024 11:48:05 +0200 Subject: [PATCH 023/195] Fix template location --- src/pretalx/orga/templates/orga/{ => admin}/update.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/pretalx/orga/templates/orga/{ => admin}/update.html (100%) diff --git a/src/pretalx/orga/templates/orga/update.html b/src/pretalx/orga/templates/orga/admin/update.html similarity index 100% rename from src/pretalx/orga/templates/orga/update.html rename to src/pretalx/orga/templates/orga/admin/update.html From 41392f3fa46e77c62a434e49d5f42e541e30fac5 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Mon, 22 Apr 2024 12:13:52 +0200 Subject: [PATCH 024/195] Small fixes to #1732 --- doc/changelog.rst | 4 +++ .../src/components/Session.vue | 30 +++++++++++-------- src/pretalx/orga/forms/submission.py | 12 +++----- src/pretalx/orga/views/speaker.py | 12 ++------ src/pretalx/orga/views/submission.py | 13 ++++---- src/pretalx/person/permissions.py | 4 +-- src/pretalx/submission/forms/submission.py | 3 +- src/pretalx/submission/models/submission.py | 10 +++++-- 8 files changed, 42 insertions(+), 46 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 92c87dd1d..af75436aa 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,6 +6,7 @@ Release Notes <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD - :feature:`schedule,1794` The iCal schedule export has been made private (available only to organisers) as the utility of importing a conference's entire schedule is limited, and people were frustrated that the iCal export did not reflect any applied schedule filters. - :bug:`schedule,1803` The QR code for schedule exporter links was not showing up when hovering on the QR code symbol. - :release:`2024.2.1 <2024-08-07>` @@ -21,6 +22,9 @@ Release Notes - :feature:`orga:schedule,1730` The schedule editor now allows you to schedule talks that are only "pending accepted" (i.e. the speaker has not yet received the acceptance email), so that organisers can try out how their schedule would look with a given number of tentatively accepted proposals. ======= >>>>>>> 06d76a0bd (Add new user list for administrators) +======= +- :feature:`orga:schedule` The schedule editor now allows you to schedule talks that are only "pending accepted" (i.e. the speaker has not yet received the acceptance email), so that organisers can try out how their schedule would look with a given number of tentatively accepted proposals. +>>>>>>> febf47ce8 (Small fixes to #1732) - :feature:`orga` Administrators (i.e. instance owners) can now search a list of all users, which includes their teams and permissions, and links to trigger account deletion and password resets. - :bug:`orga:review` Assigning reviewers could lead to incorrect assignments when browsers cached the form, but new reviewers were added to the team, shifting the overall order of input fields. - :feature:`cfp` Choice and multiple choice questions now use a drop-down with typeahead (search for options) when they have a lot of options. diff --git a/src/pretalx/frontend/schedule-editor/src/components/Session.vue b/src/pretalx/frontend/schedule-editor/src/components/Session.vue index 6e6f0c131..955e0bf14 100644 --- a/src/pretalx/frontend/schedule-editor/src/components/Session.vue +++ b/src/pretalx/frontend/schedule-editor/src/components/Session.vue @@ -8,9 +8,9 @@ .info .title {{ getLocalizedString(session.title) }} .speakers(v-if="session.speakers") {{ session.speakers.map(s => s.name).join(', ') }} - .pending-line(v-if="session.state && session.state !== 'confirmed' && session.state !== 'accepted'") + .pending-line(v-if="session.state && session.state !== 'confirmed' && session.state !== 'accepted'") i.fa.fa-exclamation-circle - span {{ $t('Pending acceptance/confirmation') }} + span {{ $t('Pending proposal state') }} .bottom-info(v-if="!isBreak") .track(v-if="session.track") {{ getLocalizedString(session.track.name) }} .warning.no-print(v-if="warnings?.length") @@ -67,8 +67,8 @@ export default { if (this.isBreak) classes.push('isbreak') else { classes.push('istalk') - if (this.session.state !== "confirmed" && this.session.state !== "accepted") classes.push('pending') - else if (this.session.state !== "confirmed") classes.push('unconfirmed') + if (this.session.state !== "confirmed" && this.session.state !== "accepted") classes.push('pending') + else if (this.session.state !== "confirmed") classes.push('unconfirmed') } if (this.isDragged) classes.push('dragging') if (this.isDragClone) classes.push('clone') @@ -129,11 +129,6 @@ export default { &.dragging filter: opacity(0.3) cursor: inherit - &.unconfirmed - .time-box - opacity: 0.5 - .info - background-image: repeating-linear-gradient(-38deg, $clr-grey-100, $clr-grey-100 10px, $clr-white 10px, $clr-white 20px) &.isbreak background-color: $clr-grey-200 border-radius: 6px @@ -171,11 +166,19 @@ export default { border-left: none .title color: var(--pretalx-clr-primary) - &.pending + &.pending, &.unconfirmed .time-box opacity: 0.5 .info background-image: repeating-linear-gradient(-38deg, $clr-grey-100, $clr-grey-100 10px, $clr-white 10px, $clr-white 20px) + &:hover + .info + border: 1px solid var(--track-color) + border-left: none + .title + color: var(--pretalx-clr-primary) + &.pending + .info border-style: dashed dashed dashed none .time-box width: 69px @@ -216,9 +219,10 @@ export default { color: var(--track-color) ellipsis() margin-right: 4px - .pending-line - span - margin-left: 1em + .pending-line + color: $clr-warning + .fa + margin-right: 4px .warning position: absolute top: 0 diff --git a/src/pretalx/orga/forms/submission.py b/src/pretalx/orga/forms/submission.py index f6bce02f0..0fdf6ea28 100644 --- a/src/pretalx/orga/forms/submission.py +++ b/src/pretalx/orga/forms/submission.py @@ -88,9 +88,9 @@ def __init__(self, event, anonymise=False, **kwargs): choices=SubmissionStates.get_choices(), initial=SubmissionStates.SUBMITTED, ) - if not self.instance.pk or self.instance.state in ( - SubmissionStates.ACCEPTED, - SubmissionStates.CONFIRMED, + if ( + not self.instance.pk + or self.instance.state in SubmissionStates.accepted_states ): self.fields["room"] = forms.ModelChoiceField( required=False, @@ -171,11 +171,7 @@ def save(self, *args, **kwargs): if "slot_count" in self.changed_data and "slot_count" in self.initial: instance.update_talk_slots() if ( - instance.state - in ( - SubmissionStates.ACCEPTED, - SubmissionStates.CONFIRMED, - ) + instance.state in SubmissionStates.accepted_states and self.cleaned_data.get("room") and self.cleaned_data.get("start") and any(field in self.changed_data for field in ("room", "start", "end")) diff --git a/src/pretalx/orga/views/speaker.py b/src/pretalx/orga/views/speaker.py index e096b54b8..224bde0ef 100644 --- a/src/pretalx/orga/views/speaker.py +++ b/src/pretalx/orga/views/speaker.py @@ -84,7 +84,7 @@ def get_queryset(self): accepted_submission_count=Count( "user__submissions", filter=Q(user__submissions__event=self.request.event) - & Q(user__submissions__state__in=["accepted", "confirmed"]), + & Q(user__submissions__state__in=SubmissionStates.accepted_states), ), ) ) @@ -94,19 +94,13 @@ def get_queryset(self): if self.request.GET["role"] == "true": qs = qs.filter( user__submissions__in=self.request.event.submissions.filter( - state__in=[ - SubmissionStates.ACCEPTED, - SubmissionStates.CONFIRMED, - ] + state__in=SubmissionStates.accepted_states ) ) elif self.request.GET["role"] == "false": qs = qs.exclude( user__submissions__in=self.request.event.submissions.filter( - state__in=[ - SubmissionStates.ACCEPTED, - SubmissionStates.CONFIRMED, - ] + state__in=SubmissionStates.accepted_states ) ) diff --git a/src/pretalx/orga/views/submission.py b/src/pretalx/orga/views/submission.py index 2ef4dd490..80bd975ae 100644 --- a/src/pretalx/orga/views/submission.py +++ b/src/pretalx/orga/views/submission.py @@ -225,10 +225,7 @@ def do(self, force=False, pending=False): if pending: self.object.pending_state = self._target self.object.save() - if self.object.pending_state in [ - SubmissionStates.ACCEPTED, - SubmissionStates.CONFIRMED, - ]: + if self.object.pending_state in SubmissionStates.accepted_states: # allow configureability of pending accepted/confirmed talks self.object.update_talk_slots() else: @@ -890,7 +887,7 @@ def submission_track_data(self): @context def talk_timeline_data(self): talk_ids = self.request.event.submissions.filter( - state__in=[SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED] + state__in=SubmissionStates.accepted_states ).values_list("id", flat=True) data = Counter( log.timestamp.astimezone(self.request.event.tz).date().isoformat() @@ -915,7 +912,7 @@ def talk_state_data(self): counter = Counter( submission.get_state_display() for submission in self.request.event.submissions.filter( - state__in=[SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED] + state__in=SubmissionStates.accepted_states ) ) return json.dumps( @@ -930,7 +927,7 @@ def talk_type_data(self): counter = Counter( str(submission.submission_type) for submission in self.request.event.submissions.filter( - state__in=[SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED] + state__in=SubmissionStates.accepted_states ).select_related("submission_type") ) return json.dumps( @@ -946,7 +943,7 @@ def talk_track_data(self): counter = Counter( str(submission.track) for submission in self.request.event.submissions.filter( - state__in=[SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED] + state__in=SubmissionStates.accepted_states ).select_related("track") ) return json.dumps( diff --git a/src/pretalx/person/permissions.py b/src/pretalx/person/permissions.py index 995637591..5d51f58f0 100644 --- a/src/pretalx/person/permissions.py +++ b/src/pretalx/person/permissions.py @@ -53,9 +53,7 @@ def person_can_view_information(user, obj): return qs.exists() if obj.target_group == "confirmed": return qs.filter(state=SubmissionStates.CONFIRMED).exists() - return qs.filter( - state__in=[SubmissionStates.CONFIRMED, SubmissionStates.ACCEPTED] - ).exists() + return qs.filter(state__in=SubmissionStates.accepted_states).exists() rules.add_perm("person.is_administrator", is_administrator) diff --git a/src/pretalx/submission/forms/submission.py b/src/pretalx/submission/forms/submission.py index a4909733d..8c1149785 100644 --- a/src/pretalx/submission/forms/submission.py +++ b/src/pretalx/submission/forms/submission.py @@ -150,8 +150,7 @@ def _set_slot_count(self, instance=None): elif ( "slot_count" in self.fields and instance - and instance.state - in [SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED] + and instance.state in SubmissionStates.accepted_states ): self.fields["slot_count"].disabled = True self.fields["slot_count"].help_text += " " + str( diff --git a/src/pretalx/submission/models/submission.py b/src/pretalx/submission/models/submission.py index 3f5f6f143..b05275ef4 100644 --- a/src/pretalx/submission/models/submission.py +++ b/src/pretalx/submission/models/submission.py @@ -82,6 +82,8 @@ class SubmissionStates(Choices): DELETED: "remove", } + accepted_states = (ACCEPTED, CONFIRMED) + class SubmissionManager(models.Manager): def get_queryset(self): @@ -301,7 +303,7 @@ def editable(self): ) if self.state == SubmissionStates.DRAFT: return self.cfp_open - return self.state in (SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED) + return self.state in SubmissionStates.accepted_states @property def anonymised(self): @@ -430,8 +432,10 @@ def update_talk_slots(self): """ from pretalx.schedule.models import TalkSlot - # scheduling is only allowed (and therefore slots needed) for accepted and confirmed talks, or those pending counterparts - scheduling_allowed = self.state in [SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED] or self.pending_state in [SubmissionStates.ACCEPTED, SubmissionStates.CONFIRMED] + scheduling_allowed = ( + self.state in SubmissionStates.accepted_states + or self.pending_state in SubmissionStates.accepted_states + ) if not scheduling_allowed: TalkSlot.objects.filter( From 7111138c90fd5bf817bcffffac532979150f16a5 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Mon, 22 Apr 2024 12:41:30 +0200 Subject: [PATCH 025/195] Fix invisible sessions at midnight on the first day Closes #1702 --- doc/changelog.rst | 5 +++++ src/pretalx/frontend/schedule-editor/src/App.vue | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index af75436aa..cde01b47a 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -7,6 +7,7 @@ Release Notes <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD - :feature:`schedule,1794` The iCal schedule export has been made private (available only to organisers) as the utility of importing a conference's entire schedule is limited, and people were frustrated that the iCal export did not reflect any applied schedule filters. - :bug:`schedule,1803` The QR code for schedule exporter links was not showing up when hovering on the QR code symbol. - :release:`2024.2.1 <2024-08-07>` @@ -25,6 +26,10 @@ Release Notes ======= - :feature:`orga:schedule` The schedule editor now allows you to schedule talks that are only "pending accepted" (i.e. the speaker has not yet received the acceptance email), so that organisers can try out how their schedule would look with a given number of tentatively accepted proposals. >>>>>>> febf47ce8 (Small fixes to #1732) +======= +- :bug:`orga:schedule,1702` Sessions starting at exactly midnight of the first day of the event would not show up in the schedule editor (but could be scheduled there by dropping them on the day heading). +- :feature:`orga:schedule,1730` The schedule editor now allows you to schedule talks that are only "pending accepted" (i.e. the speaker has not yet received the acceptance email), so that organisers can try out how their schedule would look with a given number of tentatively accepted proposals. +>>>>>>> 9451c6e19 (Fix invisible sessions at midnight on the first day) - :feature:`orga` Administrators (i.e. instance owners) can now search a list of all users, which includes their teams and permissions, and links to trigger account deletion and password resets. - :bug:`orga:review` Assigning reviewers could lead to incorrect assignments when browsers cached the form, but new reviewers were added to the team, shifting the overall order of input fields. - :feature:`cfp` Choice and multiple choice questions now use a drop-down with typeahead (search for options) when they have a lot of options. diff --git a/src/pretalx/frontend/schedule-editor/src/App.vue b/src/pretalx/frontend/schedule-editor/src/App.vue index 0e2f2b26a..585cf6e95 100644 --- a/src/pretalx/frontend/schedule-editor/src/App.vue +++ b/src/pretalx/frontend/schedule-editor/src/App.vue @@ -178,7 +178,7 @@ export default { sessions () { if (!this.schedule) return const sessions = [] - for (const session of this.schedule.talks.filter(s => s.start && moment(s.start).isAfter(this.days[0]) && moment(s.start).isBefore(this.days.at(-1).clone().endOf('day')))) { + for (const session of this.schedule.talks.filter(s => s.start && moment(s.start).isSameOrAfter(this.days[0]) && moment(s.start).isSameOrBefore(this.days.at(-1).clone().endOf('day')))) { sessions.push({ id: session.id, code: session.code, From a195fe9783bbcd513af2248d4702c856585e8c48 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 23 Apr 2024 15:54:20 +0200 Subject: [PATCH 026/195] Update reportlab --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 74c2c086a..d804f98c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ dependencies = [ "publicsuffixlist~=0.10.0", "python-dateutil~=2.9.0", "qrcode~=7.0", - "reportlab~=4.1.0", + "reportlab~=4.2.0", "requests~=2.31.0", "rules~=3.3.0", "urlman~=2.0.1", From 090e781ee6b75cdb9e9119cdadbfc80710b461ce Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 23 Apr 2024 15:55:05 +0200 Subject: [PATCH 027/195] Update celery --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d804f98c6..174774961 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ dependencies = [ "beautifulsoup4[lxml]~=4.12.0", "bleach~=6.1.0", - "celery~=5.3.0", + "celery~=5.4.0", "css_inline~=0.14.0", "csscompressor~=0.9.0", "cssutils~=2.10.0", From 05267a144067c30464d0be43ed19d34eb9d7c581 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 26 Apr 2024 12:30:54 +0200 Subject: [PATCH 028/195] Log event and organiser deletion --- src/pretalx/common/log_display.py | 5 +++ src/pretalx/common/models/log.py | 64 +++------------------------ src/pretalx/event/models/organiser.py | 7 ++- 3 files changed, 13 insertions(+), 63 deletions(-) diff --git a/src/pretalx/common/log_display.py b/src/pretalx/common/log_display.py index 1a384f991..3e5c20a97 100644 --- a/src/pretalx/common/log_display.py +++ b/src/pretalx/common/log_display.py @@ -18,6 +18,11 @@ SubmissionStates, ) +# Usually, we don't have to include the object name in activity log +# strings, because we use ActivityLog.content_object to get the object +# and display it above the message. However, in some cases, like when +# we log the deletion of an object, we don't have the object anymore, +# so we'll want to format the message instead. TEMPLATE_LOG_NAMES = { "pretalx.event.delete": _("The event {name} ({slug}) by {organiser} was deleted."), "pretalx.organiser.delete": _("The organiser {name} was deleted."), diff --git a/src/pretalx/common/models/log.py b/src/pretalx/common/models/log.py index d16440a3f..ab923e338 100644 --- a/src/pretalx/common/models/log.py +++ b/src/pretalx/common/models/log.py @@ -5,9 +5,6 @@ from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.functional import cached_property -from django.utils.html import escape -from django.utils.translation import gettext_lazy as _ -from django.utils.translation import ngettext_lazy as _n from django_scopes import ScopedManager @@ -75,63 +72,12 @@ def display(self): @cached_property def display_object(self) -> str: - """Returns an organiser backend URL to the object in question (if any).""" + """Returns a link (formatted HTML) to the object in question.""" from pretalx.common.signals import activitylog_object_link - from pretalx.mail.models import MailTemplate, QueuedMail - from pretalx.submission.models import ( - Answer, - AnswerOption, - CfP, - Question, - Submission, - SubmissionStates, - ) - - url = "" - text = "" - link_text = "" - if isinstance(self.content_object, Submission): - url = self.content_object.orga_urls.base - link_text = escape(self.content_object.title) - if self.content_object.state in [ - SubmissionStates.ACCEPTED, - SubmissionStates.CONFIRMED, - ]: - text = _n("Session", "Sessions", 1) - else: - text = _n("Proposal", "Proposals", 1) - if isinstance(self.content_object, Question): - url = self.content_object.urls.base - link_text = escape(self.content_object.question) - text = _("Question") - if isinstance(self.content_object, AnswerOption): - url = self.content_object.question.urls.base - link_text = escape(self.content_object.question.question) - text = _("Question") - if isinstance(self.content_object, Answer): - if self.content_object.submission: - url = self.content_object.submission.orga_urls.base - else: - url = self.content_object.question.urls.base - link_text = escape(self.content_object.question.question) - text = _("Answer to question") - if isinstance(self.content_object, CfP): - url = self.content_object.urls.text - link_text = _("CfP") - if isinstance(self.content_object, MailTemplate): - url = self.content_object.urls.base - text = _("Mail template") - link_text = escape(self.content_object.subject) - if isinstance(self.content_object, QueuedMail): - url = self.content_object.urls.base - text = _("Email") - link_text = escape(self.content_object.subject) - if url: - if not link_text: - link_text = url - return f'{text} {link_text}' - if text or link_text: - return f"{text} {link_text}" + + if not self.content_object: + return "" + responses = activitylog_object_link.send(sender=self.event, activitylog=self) if responses: for _receiver, response in responses: diff --git a/src/pretalx/event/models/organiser.py b/src/pretalx/event/models/organiser.py index 80ab2a737..1fcf75170 100644 --- a/src/pretalx/event/models/organiser.py +++ b/src/pretalx/event/models/organiser.py @@ -8,7 +8,7 @@ from django.utils.functional import cached_property from django.utils.translation import get_language from django.utils.translation import gettext_lazy as _ -from django_scopes import scope, scopes_disabled +from django_scopes import scope from i18nfield.fields import I18nCharField from pretalx.common.mixins.models import PretalxModel @@ -75,9 +75,8 @@ def shred(self, person=None): ) for event in self.events.all(): with scope(event=event): - event.shred() - with scopes_disabled(): - self.logged_actions().delete() + event.shred(person=person) + # We keep our logged actions, even with the now-broken content type self.delete() shred.alters_data = True From d39e0c49401828d5d82c671cba467822fc61bb1e Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 30 Apr 2024 12:39:39 +0200 Subject: [PATCH 029/195] Move Choices to pretalx.common.models --- src/pretalx/common/{ => models}/choices.py | 0 src/pretalx/submission/models/question.py | 4 ++-- src/pretalx/submission/models/submission.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/pretalx/common/{ => models}/choices.py (100%) diff --git a/src/pretalx/common/choices.py b/src/pretalx/common/models/choices.py similarity index 100% rename from src/pretalx/common/choices.py rename to src/pretalx/common/models/choices.py diff --git a/src/pretalx/submission/models/question.py b/src/pretalx/submission/models/question.py index 98f9faf69..a9891095e 100644 --- a/src/pretalx/submission/models/question.py +++ b/src/pretalx/submission/models/question.py @@ -5,9 +5,9 @@ from django_scopes import ScopedManager from i18nfield.fields import I18nCharField -from pretalx.common.choices import Choices from pretalx.common.mixins.models import OrderedModel, PretalxModel -from pretalx.common.text.phrases import phrases +from pretalx.common.models.choices import Choices +from pretalx.common.phrases import phrases from pretalx.common.urls import EventUrls from pretalx.common.utils import path_with_hash diff --git a/src/pretalx/submission/models/submission.py b/src/pretalx/submission/models/submission.py index b05275ef4..291ec8133 100644 --- a/src/pretalx/submission/models/submission.py +++ b/src/pretalx/submission/models/submission.py @@ -18,10 +18,10 @@ from django_scopes import ScopedManager, scopes_disabled from rest_framework import serializers -from pretalx.common.choices import Choices from pretalx.common.exceptions import SubmissionError from pretalx.common.mixins.models import GenerateCode, PretalxModel -from pretalx.common.text.phrases import phrases +from pretalx.common.models.choices import Choices +from pretalx.common.phrases import phrases from pretalx.common.urls import EventUrls from pretalx.common.utils import path_with_hash from pretalx.mail.models import MailTemplate, QueuedMail From 93f888d25ec3520815b94f1833e41f0cb3b0ce2e Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 30 Apr 2024 12:40:56 +0200 Subject: [PATCH 030/195] Add bug in console handling --- src/pretalx/common/console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pretalx/common/console.py b/src/pretalx/common/console.py index d469d26a7..fc6fa7b80 100644 --- a/src/pretalx/common/console.py +++ b/src/pretalx/common/console.py @@ -51,7 +51,7 @@ def print_line(string, box=False, bold=False, color=None, size=None): string += " " * (size - text_length - 2) alt_string += " " * (size - text_length - 2) string = f"┃ {string} ┃" - alt_string = f"| {string} |" + alt_string = f"| {alt_string} |" try: print(string) except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover From 172ea5d5af4ab451d7fae97452ab87b933a0ac91 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 30 Apr 2024 12:53:13 +0200 Subject: [PATCH 031/195] Refactor pretalx.common.forms.utils --- src/pretalx/common/forms/utils.py | 93 ------------------- src/pretalx/common/forms/validators.py | 50 ++++++++++ src/pretalx/common/mixins/forms.py | 59 ++++++++++-- .../common/forms/test_cfp_forms_utils.py | 4 +- src/tests/common/test_common_forms_utils.py | 6 +- 5 files changed, 105 insertions(+), 107 deletions(-) delete mode 100644 src/pretalx/common/forms/utils.py diff --git a/src/pretalx/common/forms/utils.py b/src/pretalx/common/forms/utils.py deleted file mode 100644 index bdb8ba60f..000000000 --- a/src/pretalx/common/forms/utils.py +++ /dev/null @@ -1,93 +0,0 @@ -import re - -from django import forms -from django.core.validators import MaxValueValidator, MinValueValidator -from django.utils.formats import date_format -from django.utils.timezone import get_current_timezone -from django.utils.translation import gettext_lazy as _ - - -def get_help_text(text, min_length, max_length, count_in="chars"): - if not min_length and not max_length: - return text - if text: - text = str(text) + " " - else: - text = "" - texts = { - "minmaxwords": _("Please write between {min_length} and {max_length} words."), - "minmaxchars": _( - "Please write between {min_length} and {max_length} characters." - ), - "minwords": _("Please write at least {min_length} words."), - "minchars": _("Please write at least {min_length} characters."), - "maxwords": _("Please write at most {max_length} words."), - "maxchars": _("Please write at most {max_length} characters."), - } - length = ("min" if min_length else "") + ("max" if max_length else "") - message = texts[length + count_in].format( - min_length=min_length, max_length=max_length - ) - return (text + str(message)).strip() - - -def validate_field_length(value, min_length, max_length, count_in): - if count_in == "chars": - # Line breaks should only be counted as one character - length = len(value.replace("\r\n", "\n")) - else: - length = len(re.findall(r"\b\w+\b", value)) - if (min_length and min_length > length) or (max_length and max_length < length): - error_message = get_help_text("", min_length, max_length, count_in) - errors = { - "chars": _("You wrote {count} characters."), - "words": _("You wrote {count} words."), - } - error_message += " " + str(errors[count_in]).format(count=length) - raise forms.ValidationError(error_message) - - -class MinDateValidator(MinValueValidator): - def __call__(self, value): - try: - return super().__call__(value) - except forms.ValidationError as e: - e.params["limit_value"] = date_format( - e.params["limit_value"], "SHORT_DATE_FORMAT" - ) - raise e - - -class MinDateTimeValidator(MinValueValidator): - def __call__(self, value): - try: - return super().__call__(value) - except forms.ValidationError as e: - e.params["limit_value"] = date_format( - e.params["limit_value"].astimezone(get_current_timezone()), - "SHORT_DATETIME_FORMAT", - ) - raise e - - -class MaxDateValidator(MaxValueValidator): - def __call__(self, value): - try: - return super().__call__(value) - except forms.ValidationError as e: - e.params["limit_value"] = date_format( - e.params["limit_value"], "SHORT_DATE_FORMAT" - ) - raise e - - -class MaxDateTimeValidator(MaxValueValidator): - def __call__(self, value): - try: - return super().__call__(value) - except forms.ValidationError as e: - e.params["limit_value"] = date_format( - e.params["limit_value"].astimezone(get_current_timezone()), - "SHORT_DATETIME_FORMAT", - ) - raise e diff --git a/src/pretalx/common/forms/validators.py b/src/pretalx/common/forms/validators.py index fc4542004..de8832501 100644 --- a/src/pretalx/common/forms/validators.py +++ b/src/pretalx/common/forms/validators.py @@ -1,4 +1,8 @@ +from django import forms from django.core.exceptions import ValidationError +from django.core.validators import MaxValueValidator, MinValueValidator +from django.utils.formats import date_format +from django.utils.timezone import get_current_timezone from django.utils.translation import gettext_lazy as _ from zxcvbn import zxcvbn @@ -24,3 +28,49 @@ def validate(self, password, user=None): if results.get("score", 0) < self.min_score: feedback = ", ".join(results.get("feedback", {}).get("suggestions", [])) raise ValidationError(_(feedback), params={}) + + +class MinDateValidator(MinValueValidator): + def __call__(self, value): + try: + return super().__call__(value) + except forms.ValidationError as e: + e.params["limit_value"] = date_format( + e.params["limit_value"], "SHORT_DATE_FORMAT" + ) + raise e + + +class MinDateTimeValidator(MinValueValidator): + def __call__(self, value): + try: + return super().__call__(value) + except forms.ValidationError as e: + e.params["limit_value"] = date_format( + e.params["limit_value"].astimezone(get_current_timezone()), + "SHORT_DATETIME_FORMAT", + ) + raise e + + +class MaxDateValidator(MaxValueValidator): + def __call__(self, value): + try: + return super().__call__(value) + except forms.ValidationError as e: + e.params["limit_value"] = date_format( + e.params["limit_value"], "SHORT_DATE_FORMAT" + ) + raise e + + +class MaxDateTimeValidator(MaxValueValidator): + def __call__(self, value): + try: + return super().__call__(value) + except forms.ValidationError as e: + e.params["limit_value"] = date_format( + e.params["limit_value"].astimezone(get_current_timezone()), + "SHORT_DATETIME_FORMAT", + ) + raise e diff --git a/src/pretalx/common/mixins/forms.py b/src/pretalx/common/mixins/forms.py index c9838a294..3f513935b 100644 --- a/src/pretalx/common/mixins/forms.py +++ b/src/pretalx/common/mixins/forms.py @@ -1,4 +1,5 @@ import logging +import re from functools import partial import dateutil.parser @@ -12,13 +13,11 @@ from i18nfield.forms import I18nFormField from pretalx.common.forms.fields import ExtensionFileField -from pretalx.common.forms.utils import ( +from pretalx.common.forms.validators import ( MaxDateTimeValidator, MaxDateValidator, MinDateTimeValidator, MinDateValidator, - get_help_text, - validate_field_length, ) from pretalx.common.templatetags.rich_text import rich_text from pretalx.common.text.phrases import phrases @@ -80,14 +79,14 @@ def __init__(self, *args, **kwargs): field.widget.attrs["maxlength"] = max_value field.validators.append( partial( - validate_field_length, + self.validate_field_length, min_length=min_value, max_length=max_value, count_in=self.event.cfp.settings["count_length_in"], ) ) field.original_help_text = getattr(field, "original_help_text", "") - field.added_help_text = get_help_text( + field.added_help_text = self.get_help_text( "", min_value, max_value, @@ -97,6 +96,48 @@ def __init__(self, *args, **kwargs): field.original_help_text + " " + field.added_help_text ) + @staticmethod + def get_help_text(text, min_length, max_length, count_in="chars"): + if not min_length and not max_length: + return text + if text: + text = str(text) + " " + else: + text = "" + texts = { + "minmaxwords": _( + "Please write between {min_length} and {max_length} words." + ), + "minmaxchars": _( + "Please write between {min_length} and {max_length} characters." + ), + "minwords": _("Please write at least {min_length} words."), + "minchars": _("Please write at least {min_length} characters."), + "maxwords": _("Please write at most {max_length} words."), + "maxchars": _("Please write at most {max_length} characters."), + } + length = ("min" if min_length else "") + ("max" if max_length else "") + message = texts[length + count_in].format( + min_length=min_length, max_length=max_length + ) + return (text + str(message)).strip() + + @classmethod + def validate_field_length(cls, value, min_length, max_length, count_in): + if count_in == "chars": + # Line breaks should only be counted as one character + length = len(value.replace("\r\n", "\n")) + else: + length = len(re.findall(r"\b\w+\b", value)) + if (min_length and min_length > length) or (max_length and max_length < length): + error_message = cls.get_help_text("", min_length, max_length, count_in) + errors = { + "chars": _("You wrote {count} characters."), + "words": _("You wrote {count} words."), + } + error_message += " " + str(errors[count_in]).format(count=length) + raise forms.ValidationError(error_message) + class QuestionFieldsMixin: def get_field(self, *, question, initial, initial_object, readonly): @@ -145,7 +186,7 @@ def get_field(self, *, question, initial, initial_object, readonly): if question.variant == QuestionVariant.STRING: field = forms.CharField( disabled=read_only, - help_text=get_help_text( + help_text=self.get_help_text( help_text, question.min_length, question.max_length, @@ -161,7 +202,7 @@ def get_field(self, *, question, initial, initial_object, readonly): field.widget.attrs["placeholder"] = "" # XSS field.validators.append( partial( - validate_field_length, + self.validate_field_length, min_length=question.min_length, max_length=question.max_length, count_in=self.event.cfp.settings["count_length_in"], @@ -185,7 +226,7 @@ def get_field(self, *, question, initial, initial_object, readonly): required=question.required, widget=forms.Textarea, disabled=read_only, - help_text=get_help_text( + help_text=self.get_help_text( help_text, question.min_length, question.max_length, @@ -197,7 +238,7 @@ def get_field(self, *, question, initial, initial_object, readonly): ) field.validators.append( partial( - validate_field_length, + self.validate_field_length, min_length=question.min_length, max_length=question.max_length, count_in=self.event.cfp.settings["count_length_in"], diff --git a/src/tests/common/forms/test_cfp_forms_utils.py b/src/tests/common/forms/test_cfp_forms_utils.py index c520512c1..d7ed744a6 100644 --- a/src/tests/common/forms/test_cfp_forms_utils.py +++ b/src/tests/common/forms/test_cfp_forms_utils.py @@ -1,6 +1,6 @@ import pytest -from pretalx.common.forms.utils import get_help_text +from pretalx.common.forms.mixins import RequestRequire @pytest.mark.parametrize( @@ -17,4 +17,4 @@ ), ) def test_get_text_length_help_text(text, min_length, max_length, _type, warning): - assert get_help_text(text, min_length, max_length, _type) == warning + assert RequestRequire.get_help_text(text, min_length, max_length, _type) == warning diff --git a/src/tests/common/test_common_forms_utils.py b/src/tests/common/test_common_forms_utils.py index 536fe2213..895c1f1af 100644 --- a/src/tests/common/test_common_forms_utils.py +++ b/src/tests/common/test_common_forms_utils.py @@ -1,7 +1,7 @@ import pytest from django.forms import ValidationError -from pretalx.common.forms.utils import validate_field_length +from pretalx.common.forms.mixins import RequestRequire @pytest.mark.parametrize( @@ -26,7 +26,7 @@ def test_validate_field_length(value, min_length, max_length, count_in, valid): if valid: assert ( - validate_field_length( + RequestRequire.validate_field_length( value=value, min_length=min_length, max_length=max_length, @@ -36,7 +36,7 @@ def test_validate_field_length(value, min_length, max_length, count_in, valid): ) else: with pytest.raises(ValidationError): - validate_field_length( + RequestRequire.validate_field_length( value=value, min_length=min_length, max_length=max_length, From d2efbec8f343996a97c0206f80f786f84b903304 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 30 Apr 2024 13:00:12 +0200 Subject: [PATCH 032/195] Refactor pretalx.common.settings.utils --- src/pretalx/common/console.py | 52 +++++++++++++++++++++++ src/pretalx/common/settings/config.py | 11 ++++- src/pretalx/common/settings/utils.py | 61 --------------------------- src/pretalx/settings.py | 2 +- 4 files changed, 62 insertions(+), 64 deletions(-) delete mode 100644 src/pretalx/common/settings/utils.py diff --git a/src/pretalx/common/console.py b/src/pretalx/common/console.py index fc6fa7b80..ede9ed67b 100644 --- a/src/pretalx/common/console.py +++ b/src/pretalx/common/console.py @@ -1,3 +1,10 @@ +import os +import textwrap +from contextlib import suppress +from itertools import repeat +from pathlib import Path +from sys import executable + BOLD = "\033[1m" RESET = "\033[0m" UD = "│" @@ -59,3 +66,48 @@ def print_line(string, box=False, bold=False, color=None, size=None): print(alt_string) except (UnicodeDecodeError, UnicodeEncodeError): print("unprintable setting") + + +def log_initial(*, debug, config_files, db_name, db_backend, LOG_DIR, plugins): + from pretalx import __version__ + + with suppress(Exception): # geteuid is not available on all OS + if os.geteuid() == 0: + print_line("You are running pretalx as root, why?", bold=True) + + lines = [ + (f"pretalx v{__version__}", True), + (f'Settings: {", ".join(config_files)}', False), + (f"Database: {db_name} ({db_backend})", False), + (f"Logging: {LOG_DIR}", False), + (f"Root dir: {Path(__file__).parent.parent.parent}", False), + (f"Python: {executable}", False), + ] + if plugins: + plugin_lines = textwrap.wrap(", ".join(plugins), width=92) + lines.append((f"Plugins: {plugin_lines[0]}", False)) + lines += [(" " * 11 + line, False) for line in plugin_lines[1:]] + if debug: + lines += [("DEVELOPMENT MODE, DO NOT USE IN PRODUCTION!", True)] + image = """ +┏━━━━━━━━━━┓ +┃ ┌─·──╮ ┃ +┃ │ O │ ┃ +┃ │ ┌──╯ ┃ +┃ └─┘ ┃ +┗━━━┯━┯━━━━┛ + ╰─╯ + """.strip().split( + "\n" + ) + img_width = len(image[0]) + image[-1] += " " * (img_width - len(image[-1])) + image += [" " * img_width for _ in repeat(None, (len(lines) - len(image)))] + + lines = [(f"{image[n]} {line[0]}", line[1]) for n, line in enumerate(lines)] + + size = max(len(line[0]) for line in lines) + 4 + start_box(size) + for line in lines: + print_line(line[0], box=True, bold=line[1], size=size) + end_box(size) diff --git a/src/pretalx/common/settings/config.py b/src/pretalx/common/settings/config.py index 024d884bb..ee9a30aca 100644 --- a/src/pretalx/common/settings/config.py +++ b/src/pretalx/common/settings/config.py @@ -3,8 +3,6 @@ import sys from pathlib import Path -from pretalx.common.settings.utils import reduce_dict - CONFIG = { "filesystem": { "base": { @@ -172,6 +170,15 @@ def read_config_files(config): ) # .read() returns None if there are no config files +def reduce_dict(data): + return { + section_name: { + key: value for key, value in section_content.items() if value is not None + } + for section_name, section_content in data.items() + } + + def read_layer(layer_name, config): config_dict = reduce_dict( { diff --git a/src/pretalx/common/settings/utils.py b/src/pretalx/common/settings/utils.py deleted file mode 100644 index 0dfdd8500..000000000 --- a/src/pretalx/common/settings/utils.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -import textwrap -from contextlib import suppress -from itertools import repeat -from pathlib import Path -from sys import executable - - -def log_initial(*, debug, config_files, db_name, db_backend, LOG_DIR, plugins): - from pretalx import __version__ - from pretalx.common.console import end_box, print_line, start_box - - with suppress(Exception): # geteuid is not available on all OS - if os.geteuid() == 0: - print_line("You are running pretalx as root, why?", bold=True) - - lines = [ - (f"pretalx v{__version__}", True), - (f'Settings: {", ".join(config_files)}', False), - (f"Database: {db_name} ({db_backend})", False), - (f"Logging: {LOG_DIR}", False), - (f"Root dir: {Path(__file__).parent.parent.parent}", False), - (f"Python: {executable}", False), - ] - if plugins: - plugin_lines = textwrap.wrap(", ".join(plugins), width=92) - lines.append((f"Plugins: {plugin_lines[0]}", False)) - lines += [(" " * 11 + line, False) for line in plugin_lines[1:]] - if debug: - lines += [("DEVELOPMENT MODE, DO NOT USE IN PRODUCTION!", True)] - image = """ -┏━━━━━━━━━━┓ -┃ ┌─·──╮ ┃ -┃ │ O │ ┃ -┃ │ ┌──╯ ┃ -┃ └─┘ ┃ -┗━━━┯━┯━━━━┛ - ╰─╯ - """.strip().split( - "\n" - ) - img_width = len(image[0]) - image[-1] += " " * (img_width - len(image[-1])) - image += [" " * img_width for _ in repeat(None, (len(lines) - len(image)))] - - lines = [(f"{image[n]} {line[0]}", line[1]) for n, line in enumerate(lines)] - - size = max(len(line[0]) for line in lines) + 4 - start_box(size) - for line in lines: - print_line(line[0], box=True, bold=line[1], size=size) - end_box(size) - - -def reduce_dict(data): - return { - section_name: { - key: value for key, value in section_content.items() if value is not None - } - for section_name, section_content in data.items() - } diff --git a/src/pretalx/settings.py b/src/pretalx/settings.py index 93cfebdc2..90d553bfa 100644 --- a/src/pretalx/settings.py +++ b/src/pretalx/settings.py @@ -10,8 +10,8 @@ from pkg_resources import iter_entry_points from pretalx import __version__ +from pretalx.common.console import log_initial from pretalx.common.settings.config import build_config -from pretalx.common.settings.utils import log_initial config, CONFIG_FILES = build_config() CONFIG = config From 1f77dbb77b428222ab4dd42f59c4321e02d8dbdf Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 30 Apr 2024 13:02:19 +0200 Subject: [PATCH 033/195] Exclude devserver from update checks --- src/pretalx/common/update_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pretalx/common/update_check.py b/src/pretalx/common/update_check.py index 17f230f98..2f919baac 100644 --- a/src/pretalx/common/update_check.py +++ b/src/pretalx/common/update_check.py @@ -46,7 +46,7 @@ def update_check(): if not gs.settings.update_check_id: gs.settings.set("update_check_id", uuid.uuid4().hex) - if "runserver" in sys.argv: # pragma: no cover + if "runserver" in sys.argv or "devserver" in sys.argv: # pragma: no cover gs.settings.set("update_check_last", now()) gs.settings.set("update_check_result", {"error": "development"}) return From 6df4dbc2b6549ca8d56096a21658f92839357f43 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 30 Apr 2024 13:32:42 +0200 Subject: [PATCH 034/195] Refactor pretalx.common.utils, add pretalx.common.text --- .../commands/export_schedule_html.py | 2 +- src/pretalx/agenda/views/schedule.py | 2 + src/pretalx/agenda/views/speaker.py | 2 +- src/pretalx/agenda/views/talk.py | 1 + src/pretalx/agenda/views/widget.py | 2 +- src/pretalx/cfp/flow.py | 2 + src/pretalx/cfp/views/locale.py | 2 + src/pretalx/common/console.py | 113 --------------- src/pretalx/common/context_processors.py | 2 + src/pretalx/common/language.py | 13 +- src/pretalx/common/models/transaction.py | 29 ++++ src/pretalx/common/phrases.py | 89 ------------ src/pretalx/common/text/__init__.py | 0 src/pretalx/common/{ => text}/css.py | 0 src/pretalx/common/text/daterange.py | 70 +++++++++ src/pretalx/common/text/path.py | 15 ++ src/pretalx/common/{ => text}/serialize.py | 10 ++ src/pretalx/common/utils.py | 133 ------------------ src/pretalx/event/models/event.py | 3 +- src/pretalx/orga/forms/event.py | 2 +- src/pretalx/orga/forms/mails.py | 2 +- src/pretalx/orga/views/cfp.py | 2 +- src/pretalx/orga/views/mails.py | 2 +- src/pretalx/orga/views/schedule.py | 2 +- src/pretalx/person/models/information.py | 2 +- src/pretalx/person/models/user.py | 2 +- src/pretalx/schedule/ascii.py | 2 +- src/pretalx/schedule/models/slot.py | 3 +- src/pretalx/settings.py | 2 +- src/pretalx/submission/models/question.py | 4 +- src/pretalx/submission/models/resource.py | 2 +- src/pretalx/submission/models/submission.py | 7 +- src/tests/common/test_cfp_serialize.py | 2 +- src/tests/common/test_common_console.py | 2 +- src/tests/common/test_common_css.py | 2 +- src/tests/common/test_common_utils.py | 8 +- 36 files changed, 174 insertions(+), 364 deletions(-) delete mode 100644 src/pretalx/common/console.py create mode 100644 src/pretalx/common/models/transaction.py delete mode 100644 src/pretalx/common/phrases.py create mode 100644 src/pretalx/common/text/__init__.py rename src/pretalx/common/{ => text}/css.py (100%) create mode 100644 src/pretalx/common/text/daterange.py create mode 100644 src/pretalx/common/text/path.py rename src/pretalx/common/{ => text}/serialize.py (60%) diff --git a/src/pretalx/agenda/management/commands/export_schedule_html.py b/src/pretalx/agenda/management/commands/export_schedule_html.py index 5bbe62e3f..f8103cde7 100644 --- a/src/pretalx/agenda/management/commands/export_schedule_html.py +++ b/src/pretalx/agenda/management/commands/export_schedule_html.py @@ -12,8 +12,8 @@ from django.utils.timezone import override as override_timezone from django_scopes import scope, scopes_disabled +from pretalx.common.models.transaction import rolledback_transaction from pretalx.common.signals import register_data_exporters -from pretalx.common.utils import rolledback_transaction from pretalx.event.models import Event diff --git a/src/pretalx/agenda/views/schedule.py b/src/pretalx/agenda/views/schedule.py index 540df45af..e79068bb6 100644 --- a/src/pretalx/agenda/views/schedule.py +++ b/src/pretalx/agenda/views/schedule.py @@ -22,6 +22,8 @@ from pretalx.common.mixins.views import EventPermissionRequired from pretalx.common.signals import register_data_exporters, register_my_data_exporters from pretalx.common.utils import safe_filename +from pretalx.common.signals import register_data_exporters +from pretalx.common.text.path import safe_filename from pretalx.schedule.ascii import draw_ascii_schedule from pretalx.schedule.exporters import ScheduleData from pretalx.submission.models.submission import SubmissionFavourite diff --git a/src/pretalx/agenda/views/speaker.py b/src/pretalx/agenda/views/speaker.py index a802ad7a9..03ba87e13 100644 --- a/src/pretalx/agenda/views/speaker.py +++ b/src/pretalx/agenda/views/speaker.py @@ -19,7 +19,7 @@ PermissionRequired, SocialMediaCardMixin, ) -from pretalx.common.utils import safe_filename +from pretalx.common.text.path import safe_filename from pretalx.person.models import SpeakerProfile, User from pretalx.submission.models import QuestionTarget diff --git a/src/pretalx/agenda/views/talk.py b/src/pretalx/agenda/views/talk.py index 3cbc61e98..bbb2a7da4 100644 --- a/src/pretalx/agenda/views/talk.py +++ b/src/pretalx/agenda/views/talk.py @@ -22,6 +22,7 @@ PermissionRequired, SocialMediaCardMixin, ) +from pretalx.common.mixins.views import PermissionRequired, SocialMediaCardMixin from pretalx.common.text.phrases import phrases from pretalx.schedule.models import Schedule, TalkSlot from pretalx.submission.forms import FeedbackForm diff --git a/src/pretalx/agenda/views/widget.py b/src/pretalx/agenda/views/widget.py index 64ec9b2d4..1d78caaab 100644 --- a/src/pretalx/agenda/views/widget.py +++ b/src/pretalx/agenda/views/widget.py @@ -11,7 +11,7 @@ from i18nfield.utils import I18nJSONEncoder from pretalx.agenda.views.schedule import ScheduleView -from pretalx.common.utils import language +from pretalx.common.language import language from pretalx.common.views import conditional_cache_page from pretalx.schedule.exporters import ScheduleData diff --git a/src/pretalx/cfp/flow.py b/src/pretalx/cfp/flow.py index 710163691..0bc73aad5 100644 --- a/src/pretalx/cfp/flow.py +++ b/src/pretalx/cfp/flow.py @@ -26,6 +26,8 @@ from pretalx.common.exceptions import SendMailException from pretalx.common.text.phrases import phrases from pretalx.common.utils import language +from pretalx.common.language import language +from pretalx.common.text.phrases import phrases from pretalx.person.forms import SpeakerProfileForm, UserForm from pretalx.person.models import User from pretalx.submission.forms import InfoForm, QuestionsForm diff --git a/src/pretalx/cfp/views/locale.py b/src/pretalx/cfp/views/locale.py index 75ca8e2d7..cc6d6456a 100644 --- a/src/pretalx/cfp/views/locale.py +++ b/src/pretalx/cfp/views/locale.py @@ -9,6 +9,8 @@ from django.utils.translation import override from django.views.generic import View +from pretalx.common.text.phrases import phrases + class LocaleSet(View): def get(self, request, *args, **kwargs): diff --git a/src/pretalx/common/console.py b/src/pretalx/common/console.py deleted file mode 100644 index ede9ed67b..000000000 --- a/src/pretalx/common/console.py +++ /dev/null @@ -1,113 +0,0 @@ -import os -import textwrap -from contextlib import suppress -from itertools import repeat -from pathlib import Path -from sys import executable - -BOLD = "\033[1m" -RESET = "\033[0m" -UD = "│" -LR = "─" -SEPARATORS = { - (False, True, True, False): "┬", - (True, False, False, True): "┴", - (False, False, True, True): "┤", - (True, True, False, False): "├", - (False, True, False, True): "┼", - (True, False, True, False): "┼", -} - - -def get_separator(*args): - """(upright, downright, downleft, upleft): Tuple[bool] -> separator: - - str. - """ - if sum(args) >= 3: - return "┼" - if sum(args) == 1: - return ("└", "┌", "┐", "┘")[args.index(True)] - return SEPARATORS[tuple(args)] - - -def start_box(size): - try: - print("┏" + "━" * size + "┓") - except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - print("-" * (size + 2)) - - -def end_box(size): - try: - print("┗" + "━" * size + "┛") - except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - print("-" * (size + 2)) - - -def print_line(string, box=False, bold=False, color=None, size=None): - text_length = len(string) - alt_string = string - if bold: - string = f"{BOLD}{string}{RESET}" - if color: - string = f"{color}{string}{RESET}" - if box: - if size: - if text_length + 2 < size: - string += " " * (size - text_length - 2) - alt_string += " " * (size - text_length - 2) - string = f"┃ {string} ┃" - alt_string = f"| {alt_string} |" - try: - print(string) - except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - try: - print(alt_string) - except (UnicodeDecodeError, UnicodeEncodeError): - print("unprintable setting") - - -def log_initial(*, debug, config_files, db_name, db_backend, LOG_DIR, plugins): - from pretalx import __version__ - - with suppress(Exception): # geteuid is not available on all OS - if os.geteuid() == 0: - print_line("You are running pretalx as root, why?", bold=True) - - lines = [ - (f"pretalx v{__version__}", True), - (f'Settings: {", ".join(config_files)}', False), - (f"Database: {db_name} ({db_backend})", False), - (f"Logging: {LOG_DIR}", False), - (f"Root dir: {Path(__file__).parent.parent.parent}", False), - (f"Python: {executable}", False), - ] - if plugins: - plugin_lines = textwrap.wrap(", ".join(plugins), width=92) - lines.append((f"Plugins: {plugin_lines[0]}", False)) - lines += [(" " * 11 + line, False) for line in plugin_lines[1:]] - if debug: - lines += [("DEVELOPMENT MODE, DO NOT USE IN PRODUCTION!", True)] - image = """ -┏━━━━━━━━━━┓ -┃ ┌─·──╮ ┃ -┃ │ O │ ┃ -┃ │ ┌──╯ ┃ -┃ └─┘ ┃ -┗━━━┯━┯━━━━┛ - ╰─╯ - """.strip().split( - "\n" - ) - img_width = len(image[0]) - image[-1] += " " * (img_width - len(image[-1])) - image += [" " * img_width for _ in repeat(None, (len(lines) - len(image)))] - - lines = [(f"{image[n]} {line[0]}", line[1]) for n, line in enumerate(lines)] - - size = max(len(line[0]) for line in lines) + 4 - start_box(size) - for line in lines: - print_line(line[0], box=True, bold=line[1], size=size) - end_box(size) diff --git a/src/pretalx/common/context_processors.py b/src/pretalx/common/context_processors.py index 4f71a6f50..27dd90e30 100644 --- a/src/pretalx/common/context_processors.py +++ b/src/pretalx/common/context_processors.py @@ -53,6 +53,8 @@ def locale_context(request): def messages(request): + from pretalx.common.text.phrases import phrases + return {"phrases": phrases} diff --git a/src/pretalx/common/language.py b/src/pretalx/common/language.py index 8c80296e2..523d3cc83 100644 --- a/src/pretalx/common/language.py +++ b/src/pretalx/common/language.py @@ -1,7 +1,8 @@ +import contextlib from copy import copy from django.conf import global_settings, settings -from django.utils.translation import get_language +from django.utils.translation import activate, get_language LANGUAGE_CODES_MAPPING = { language.lower(): language for language in settings.LANGUAGES_INFORMATION @@ -23,3 +24,13 @@ def get_language_information(lang: str): def get_current_language_information(): language_code = get_language() return get_language_information(language_code) + + +@contextlib.contextmanager +def language(language_code): + previous_language = get_language() + activate(language_code or settings.LANGUAGE_CODE) + try: + yield + finally: + activate(previous_language) diff --git a/src/pretalx/common/models/transaction.py b/src/pretalx/common/models/transaction.py new file mode 100644 index 000000000..291cb9842 --- /dev/null +++ b/src/pretalx/common/models/transaction.py @@ -0,0 +1,29 @@ +import contextlib + +from django.db import transaction + + +@contextlib.contextmanager +def rolledback_transaction(): + """This context manager runs your code in a database transaction that will + be rolled back in the end. + + This can come in handy to simulate the effects of a database + operation that you do not actually want to perform. Note that + rollbacks are a very slow operation on most database backends. Also, + long-running transactions can slow down other operations currently + running and you should not use this in a place that is called + frequently. + """ + + class DummyRollbackException(Exception): + pass + + try: + with transaction.atomic(): + yield + raise DummyRollbackException() + except DummyRollbackException: + pass + else: # pragma: no cover + raise Exception("Invalid state, should have rolled back.") diff --git a/src/pretalx/common/phrases.py b/src/pretalx/common/phrases.py deleted file mode 100644 index 9e1254db3..000000000 --- a/src/pretalx/common/phrases.py +++ /dev/null @@ -1,89 +0,0 @@ -import random -from abc import ABCMeta - -from django.utils.translation import gettext_lazy as _ - -_phrase_book = {} - - -class PhrasesMetaClass(ABCMeta): # noqa - def __new__(mcs, class_name, bases, namespace, app): - new = super().__new__(mcs, class_name, bases, namespace) - _phrase_book[app] = new() - return new - - def __init__(cls, *args, app, **kwargs): - super().__init__(*args, **kwargs) - - -class Phrases(metaclass=PhrasesMetaClass, app=""): - def __getattribute__(self, attribute): - result = super().__getattribute__(attribute) - if isinstance(result, (list, tuple)): - return random.choice(result) - return result - - -class PhraseBook: - def __getattribute__(self, attribute): - return _phrase_book.get(attribute) - - -phrases = PhraseBook() - - -class BasePhrases(Phrases, app="base"): - """This class contains base phrases that are guaranteed to remain the same - (i.e., are not randomly chosen). - - They are still provided as a list to make it possible to combine - them with new phrases in other classes. - """ - - send = [_("Send")] - save = [_("Save")] - cancel = [_("Cancel")] - edit = [_("Edit")] - - saved = [_("Your changes have been saved.")] - - error_sending_mail = [ - _("There was an error sending the mail. Please try again later.") - ] - error_saving_changes = [ - _("We had trouble saving your input – Please see below for details. 🠯") - ] - error_permissions_action = [_("You do not have permission to perform this action.")] - - permission_denied = [ - _("Permission denied."), - _("Sorry, you do not have the required permissions to access this page."), - _("Access denied."), - ] - not_found = [ - _("Page not found."), - _("This page does not exist."), - _("Huh, I could have sworn there was something here."), - "", - _("This page is no more."), - _("This page has ceased to be."), - _("Huh."), - ] - - enter_email = _("Email address") - password_repeat = _("New password (again)") - passwords_differ = _( - "You entered two different passwords. Please enter the same one twice!" - ) - password_too_weak = [ - _("Your password is too weak or too common, please choose another one."), - _("Sorry, this password is too weak or too common, please choose another one."), - _( - "Your password is the only thing protecting your account, so please choose a strong one." - ), - ] - use_markdown = _("You can use {link_start}Markdown{link_end} here.").format( - link_start='', - link_end="", - ) - public_content = _("This content will be shown publicly.") diff --git a/src/pretalx/common/text/__init__.py b/src/pretalx/common/text/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/pretalx/common/css.py b/src/pretalx/common/text/css.py similarity index 100% rename from src/pretalx/common/css.py rename to src/pretalx/common/text/css.py diff --git a/src/pretalx/common/text/daterange.py b/src/pretalx/common/text/daterange.py new file mode 100644 index 000000000..34e37fad8 --- /dev/null +++ b/src/pretalx/common/text/daterange.py @@ -0,0 +1,70 @@ +from django.template.defaultfilters import date as _date +from django.utils.translation import get_language +from django.utils.translation import gettext_lazy as _ + + +def daterange_de(date_from, date_to): + if ( + date_from.year == date_to.year + and date_from.month == date_to.month + and date_from.day == date_to.day + ): + return str(_date(date_from, "j. F Y")) + if date_from.year == date_to.year and date_from.month == date_to.month: + return "{}.–{}".format(_date(date_from, "j"), _date(date_to, "j. F Y")) + if date_from.year == date_to.year: + return "{} – {}".format(_date(date_from, "j. F"), _date(date_to, "j. F Y")) + return "" + + +def daterange_en(date_from, date_to): + if ( + date_from.year == date_to.year + and date_from.month == date_to.month + and date_from.day == date_to.day + ): + return str(_date(date_from, "N jS, Y")) + if date_from.year == date_to.year and date_from.month == date_to.month: + return "{} – {}".format(_date(date_from, "N jS"), _date(date_to, "jS, Y")) + if date_from.year == date_to.year: + return "{} – {}".format(_date(date_from, "N jS"), _date(date_to, "N jS, Y")) + return "" + + +def daterange_es(date_from, date_to): + if ( + date_from.year == date_to.year + and date_from.month == date_to.month + and date_from.day == date_to.day + ): + return "{}".format(_date(date_from, "DATE_FORMAT")) + if date_from.year == date_to.year and date_from.month == date_to.month: + return "{} - {} de {} de {}".format( + _date(date_from, "j"), + _date(date_to, "j"), + _date(date_to, "F"), + _date(date_to, "Y"), + ) + if date_from.year == date_to.year: + return "{} de {} - {} de {} de {}".format( + _date(date_from, "j"), + _date(date_from, "F"), + _date(date_to, "j"), + _date(date_to, "F"), + _date(date_to, "Y"), + ) + return "" + + +def daterange(date_from, date_to): + language = get_language()[:2] + lookup = { + "de": daterange_de, + "en": daterange_en, + "es": daterange_es, + } + function = lookup.get(language) + result = function(date_from, date_to) if function else None + return result or _("{date_from} – {date_to}").format( + date_from=_date(date_from, "DATE_FORMAT"), date_to=_date(date_to, "DATE_FORMAT") + ) diff --git a/src/pretalx/common/text/path.py b/src/pretalx/common/text/path.py new file mode 100644 index 000000000..e8f721b7d --- /dev/null +++ b/src/pretalx/common/text/path.py @@ -0,0 +1,15 @@ +import os +import unicodedata + +from django.utils.crypto import get_random_string + + +def path_with_hash(name): + dir_name, file_name = os.path.split(name) + file_root, file_ext = os.path.splitext(file_name) + random = get_random_string(7) + return os.path.join(dir_name, f"{file_root}_{random}{file_ext}") + + +def safe_filename(filename): + return unicodedata.normalize("NFD", filename).encode("ASCII", "ignore").decode() diff --git a/src/pretalx/common/serialize.py b/src/pretalx/common/text/serialize.py similarity index 60% rename from src/pretalx/common/serialize.py rename to src/pretalx/common/text/serialize.py index 481ac1bb2..be49de999 100644 --- a/src/pretalx/common/serialize.py +++ b/src/pretalx/common/text/serialize.py @@ -1,5 +1,8 @@ import datetime as dt +from i18nfield.strings import LazyI18nString +from i18nfield.utils import I18nJSONEncoder + def serialize_duration(minutes): duration = dt.timedelta(minutes=minutes) @@ -14,3 +17,10 @@ def serialize_duration(minutes): else: fmt = f"00:{fmt}" return fmt + + +class I18nStrJSONEncoder(I18nJSONEncoder): + def default(self, obj): + if isinstance(obj, LazyI18nString): + return str(obj) + return super().default(obj) diff --git a/src/pretalx/common/utils.py b/src/pretalx/common/utils.py index 463c81008..e69de29bb 100644 --- a/src/pretalx/common/utils.py +++ b/src/pretalx/common/utils.py @@ -1,133 +0,0 @@ -import contextlib -import os -import unicodedata - -from django.conf import settings -from django.db import transaction -from django.template.defaultfilters import date as _date -from django.utils.crypto import get_random_string -from django.utils.translation import activate, get_language -from django.utils.translation import gettext_lazy as _ -from i18nfield.strings import LazyI18nString -from i18nfield.utils import I18nJSONEncoder - - -def daterange_de(date_from, date_to): - if ( - date_from.year == date_to.year - and date_from.month == date_to.month - and date_from.day == date_to.day - ): - return str(_date(date_from, "j. F Y")) - if date_from.year == date_to.year and date_from.month == date_to.month: - return "{}.–{}".format(_date(date_from, "j"), _date(date_to, "j. F Y")) - if date_from.year == date_to.year: - return "{} – {}".format(_date(date_from, "j. F"), _date(date_to, "j. F Y")) - return "" - - -def daterange_en(date_from, date_to): - if ( - date_from.year == date_to.year - and date_from.month == date_to.month - and date_from.day == date_to.day - ): - return str(_date(date_from, "N jS, Y")) - if date_from.year == date_to.year and date_from.month == date_to.month: - return "{} – {}".format(_date(date_from, "N jS"), _date(date_to, "jS, Y")) - if date_from.year == date_to.year: - return "{} – {}".format(_date(date_from, "N jS"), _date(date_to, "N jS, Y")) - return "" - - -def daterange_es(date_from, date_to): - if ( - date_from.year == date_to.year - and date_from.month == date_to.month - and date_from.day == date_to.day - ): - return "{}".format(_date(date_from, "DATE_FORMAT")) - if date_from.year == date_to.year and date_from.month == date_to.month: - return "{} - {} de {} de {}".format( - _date(date_from, "j"), - _date(date_to, "j"), - _date(date_to, "F"), - _date(date_to, "Y"), - ) - if date_from.year == date_to.year: - return "{} de {} - {} de {} de {}".format( - _date(date_from, "j"), - _date(date_from, "F"), - _date(date_to, "j"), - _date(date_to, "F"), - _date(date_to, "Y"), - ) - return "" - - -def daterange(date_from, date_to): - language = get_language()[:2] - lookup = { - "de": daterange_de, - "en": daterange_en, - "es": daterange_es, - } - function = lookup.get(language) - result = function(date_from, date_to) if function else None - return result or _("{date_from} – {date_to}").format( - date_from=_date(date_from, "DATE_FORMAT"), date_to=_date(date_to, "DATE_FORMAT") - ) - - -class I18nStrJSONEncoder(I18nJSONEncoder): - def default(self, obj): - if isinstance(obj, LazyI18nString): - return str(obj) - return super().default(obj) - - -def path_with_hash(name): - dir_name, file_name = os.path.split(name) - file_root, file_ext = os.path.splitext(file_name) - random = get_random_string(7) - return os.path.join(dir_name, f"{file_root}_{random}{file_ext}") - - -@contextlib.contextmanager -def rolledback_transaction(): - """This context manager runs your code in a database transaction that will - be rolled back in the end. - - This can come in handy to simulate the effects of a database - operation that you do not actually want to perform. Note that - rollbacks are a very slow operation on most database backends. Also, - long-running transactions can slow down other operations currently - running and you should not use this in a place that is called - frequently. - """ - - class DummyRollbackException(Exception): - pass - - try: - with transaction.atomic(): - yield - raise DummyRollbackException() - except DummyRollbackException: - pass - else: # pragma: no cover - raise Exception("Invalid state, should have rolled back.") - - -@contextlib.contextmanager -def language(language_code): - previous_language = get_language() - activate(language_code or settings.LANGUAGE_CODE) - try: - yield - finally: - activate(previous_language) - - -def safe_filename(filename): - return unicodedata.normalize("NFD", filename).encode("ASCII", "ignore").decode() diff --git a/src/pretalx/event/models/event.py b/src/pretalx/event/models/event.py index 29e352557..7c6af26bd 100644 --- a/src/pretalx/event/models/event.py +++ b/src/pretalx/event/models/event.py @@ -22,9 +22,10 @@ from pretalx.common.models import TIMEZONE_CHOICES from pretalx.common.models.settings import hierarkey from pretalx.common.plugins import get_all_plugins +from pretalx.common.text.daterange import daterange +from pretalx.common.text.path import path_with_hash from pretalx.common.text.phrases import phrases from pretalx.common.urls import EventUrls -from pretalx.common.utils import daterange, path_with_hash # Slugs need to start and end with an alphanumeric character, # but may contain dashes and dots in between. diff --git a/src/pretalx/orga/forms/event.py b/src/pretalx/orga/forms/event.py index 020cb04eb..1e29f886d 100644 --- a/src/pretalx/orga/forms/event.py +++ b/src/pretalx/orga/forms/event.py @@ -14,7 +14,6 @@ from i18nfield.fields import I18nFormField, I18nTextarea from i18nfield.forms import I18nFormMixin, I18nModelForm -from pretalx.common.css import validate_css from pretalx.common.forms.fields import ImageField from pretalx.common.mixins.forms import ( HierarkeyMixin, @@ -22,6 +21,7 @@ JsonSubfieldMixin, ReadOnlyFlag, ) +from pretalx.common.text.css import validate_css from pretalx.common.text.phrases import phrases from pretalx.event.models.event import Event from pretalx.orga.forms.widgets import HeaderSelect, MultipleLanguagesWidget diff --git a/src/pretalx/orga/forms/mails.py b/src/pretalx/orga/forms/mails.py index 8c8126edf..894c419b3 100644 --- a/src/pretalx/orga/forms/mails.py +++ b/src/pretalx/orga/forms/mails.py @@ -10,9 +10,9 @@ from i18nfield.forms import I18nModelForm from pretalx.common.exceptions import SendMailException +from pretalx.common.language import language from pretalx.common.mixins.forms import I18nHelpText, ReadOnlyFlag from pretalx.common.templatetags.rich_text import rich_text -from pretalx.common.utils import language from pretalx.mail.context import get_available_placeholders from pretalx.mail.models import MailTemplate, QueuedMail from pretalx.mail.placeholders import SimpleFunctionalMailTextPlaceholder diff --git a/src/pretalx/orga/views/cfp.py b/src/pretalx/orga/views/cfp.py index a7793b14b..ae3b330fb 100644 --- a/src/pretalx/orga/views/cfp.py +++ b/src/pretalx/orga/views/cfp.py @@ -23,7 +23,7 @@ PaginationMixin, PermissionRequired, ) -from pretalx.common.utils import I18nStrJSONEncoder +from pretalx.common.text.serialize import I18nStrJSONEncoder from pretalx.common.views import CreateOrUpdateView, OrderModelView from pretalx.orga.forms import CfPForm, QuestionForm, SubmissionTypeForm, TrackForm from pretalx.orga.forms.cfp import ( diff --git a/src/pretalx/orga/views/mails.py b/src/pretalx/orga/views/mails.py index 090f4f20e..064d664cd 100644 --- a/src/pretalx/orga/views/mails.py +++ b/src/pretalx/orga/views/mails.py @@ -8,6 +8,7 @@ from django.views.generic import FormView, ListView, TemplateView, View from django_context_decorator import context +from pretalx.common.language import language from pretalx.common.mail import TolerantDict from pretalx.common.mixins.views import ( ActionFromUrl, @@ -18,7 +19,6 @@ Sortable, ) from pretalx.common.templatetags.rich_text import rich_text -from pretalx.common.utils import language from pretalx.common.views import CreateOrUpdateView from pretalx.mail.models import MailTemplate, QueuedMail from pretalx.orga.forms.mails import ( diff --git a/src/pretalx/orga/views/schedule.py b/src/pretalx/orga/views/schedule.py index 7d90ec12f..6f15e9ec8 100644 --- a/src/pretalx/orga/views/schedule.py +++ b/src/pretalx/orga/views/schedule.py @@ -29,7 +29,7 @@ PermissionRequired, ) from pretalx.common.signals import register_data_exporters -from pretalx.common.utils import safe_filename +from pretalx.common.text.path import safe_filename from pretalx.common.views import CreateOrUpdateView, OrderModelView from pretalx.orga.forms.schedule import ( ScheduleExportForm, diff --git a/src/pretalx/person/models/information.py b/src/pretalx/person/models/information.py index 94550d369..473a92de5 100644 --- a/src/pretalx/person/models/information.py +++ b/src/pretalx/person/models/information.py @@ -3,9 +3,9 @@ from i18nfield.fields import I18nCharField, I18nTextField from pretalx.common.mixins.models import PretalxModel +from pretalx.common.text.path import path_with_hash from pretalx.common.text.phrases import phrases from pretalx.common.urls import EventUrls -from pretalx.common.utils import path_with_hash def resource_path(instance, filename): diff --git a/src/pretalx/person/models/user.py b/src/pretalx/person/models/user.py index 71072197e..1cb528fe8 100644 --- a/src/pretalx/person/models/user.py +++ b/src/pretalx/person/models/user.py @@ -23,8 +23,8 @@ from pretalx.common.mixins.models import FileCleanupMixin, GenerateCode from pretalx.common.models import TIMEZONE_CHOICES +from pretalx.common.text.path import path_with_hash from pretalx.common.urls import build_absolute_uri -from pretalx.common.utils import path_with_hash def avatar_path(instance, filename): diff --git a/src/pretalx/schedule/ascii.py b/src/pretalx/schedule/ascii.py index 64cc35f70..abb521d95 100644 --- a/src/pretalx/schedule/ascii.py +++ b/src/pretalx/schedule/ascii.py @@ -4,7 +4,7 @@ from dateutil import rrule from django.utils.translation import gettext_lazy as _ -from pretalx.common.console import LR, UD, get_separator +from pretalx.common.text.console import LR, UD, get_separator def draw_schedule_list(data): diff --git a/src/pretalx/schedule/models/slot.py b/src/pretalx/schedule/models/slot.py index cc368ea31..fe98f3942 100644 --- a/src/pretalx/schedule/models/slot.py +++ b/src/pretalx/schedule/models/slot.py @@ -14,6 +14,7 @@ from i18nfield.fields import I18nCharField from pretalx.common.mixins.models import PretalxModel +from pretalx.common.text.serialize import serialize_duration from pretalx.common.urls import get_base_url INSTANCE_IDENTIFIER = None @@ -80,8 +81,6 @@ def duration(self) -> int: @cached_property def export_duration(self): - from pretalx.common.serialize import serialize_duration - return serialize_duration(minutes=self.duration) @cached_property diff --git a/src/pretalx/settings.py b/src/pretalx/settings.py index 90d553bfa..b035f64f5 100644 --- a/src/pretalx/settings.py +++ b/src/pretalx/settings.py @@ -10,7 +10,7 @@ from pkg_resources import iter_entry_points from pretalx import __version__ -from pretalx.common.console import log_initial +from pretalx.common.text.console import log_initial from pretalx.common.settings.config import build_config config, CONFIG_FILES = build_config() diff --git a/src/pretalx/submission/models/question.py b/src/pretalx/submission/models/question.py index a9891095e..a07528986 100644 --- a/src/pretalx/submission/models/question.py +++ b/src/pretalx/submission/models/question.py @@ -7,9 +7,9 @@ from pretalx.common.mixins.models import OrderedModel, PretalxModel from pretalx.common.models.choices import Choices -from pretalx.common.phrases import phrases +from pretalx.common.text.path import path_with_hash +from pretalx.common.text.phrases import phrases from pretalx.common.urls import EventUrls -from pretalx.common.utils import path_with_hash def answer_file_path(instance, filename): diff --git a/src/pretalx/submission/models/resource.py b/src/pretalx/submission/models/resource.py index 6be6fc865..007779c5f 100644 --- a/src/pretalx/submission/models/resource.py +++ b/src/pretalx/submission/models/resource.py @@ -7,8 +7,8 @@ from django_scopes import ScopedManager from pretalx.common.mixins.models import PretalxModel +from pretalx.common.text.path import path_with_hash from pretalx.common.urls import get_base_url -from pretalx.common.utils import path_with_hash def resource_path(instance, filename): diff --git a/src/pretalx/submission/models/submission.py b/src/pretalx/submission/models/submission.py index 291ec8133..e32387aaf 100644 --- a/src/pretalx/submission/models/submission.py +++ b/src/pretalx/submission/models/submission.py @@ -21,9 +21,10 @@ from pretalx.common.exceptions import SubmissionError from pretalx.common.mixins.models import GenerateCode, PretalxModel from pretalx.common.models.choices import Choices -from pretalx.common.phrases import phrases +from pretalx.common.text.path import path_with_hash +from pretalx.common.text.phrases import phrases +from pretalx.common.text.serialize import serialize_duration from pretalx.common.urls import EventUrls -from pretalx.common.utils import path_with_hash from pretalx.mail.models import MailTemplate, QueuedMail from pretalx.person.models import User from pretalx.submission.signals import submission_state_change @@ -831,8 +832,6 @@ def __str__(self): @cached_property def export_duration(self): - from pretalx.common.serialize import serialize_duration - return serialize_duration(minutes=self.get_duration()) @cached_property diff --git a/src/tests/common/test_cfp_serialize.py b/src/tests/common/test_cfp_serialize.py index a2abbe656..4cd035162 100644 --- a/src/tests/common/test_cfp_serialize.py +++ b/src/tests/common/test_cfp_serialize.py @@ -1,6 +1,6 @@ import pytest -from pretalx.common.serialize import serialize_duration +from pretalx.common.text.serialize import serialize_duration @pytest.mark.parametrize( diff --git a/src/tests/common/test_common_console.py b/src/tests/common/test_common_console.py index 13228d8a4..f14516960 100644 --- a/src/tests/common/test_common_console.py +++ b/src/tests/common/test_common_console.py @@ -1,6 +1,6 @@ import pytest -from pretalx.common.console import get_separator, print_line +from pretalx.common.text.console import get_separator, print_line @pytest.mark.parametrize( diff --git a/src/tests/common/test_common_css.py b/src/tests/common/test_common_css.py index ff5d7c7b4..11f3e9473 100644 --- a/src/tests/common/test_common_css.py +++ b/src/tests/common/test_common_css.py @@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError from django.test import override_settings -from pretalx.common.css import validate_css +from pretalx.common.text.css import validate_css from pretalx.event.models import Event diff --git a/src/tests/common/test_common_utils.py b/src/tests/common/test_common_utils.py index 2ca95bfca..8d75c635d 100644 --- a/src/tests/common/test_common_utils.py +++ b/src/tests/common/test_common_utils.py @@ -4,7 +4,9 @@ from django.utils import translation from i18nfield.strings import LazyI18nString -from pretalx.common.utils import I18nStrJSONEncoder, daterange, safe_filename +from pretalx.common.text.serialize import I18nStrJSONEncoder +from pretalx.common.text.daterange import daterange +from pretalx.common.text.path import safe_filename @pytest.mark.parametrize( @@ -49,8 +51,8 @@ def test_daterange(locale, start, end, result): ), ) def test_path_with_hash(path, expected, monkeypatch): - monkeypatch.setattr("pretalx.common.utils.get_random_string", lambda x: "aaaaaaa") - from pretalx.common.utils import path_with_hash + monkeypatch.setattr("pretalx.common.text.path.get_random_string", lambda x: "aaaaaaa") + from pretalx.common.text.path import path_with_hash assert path_with_hash(path) == expected From a0dfa2be67b7fde4c3c980259423ff273b42a260 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 30 Apr 2024 13:32:57 +0200 Subject: [PATCH 035/195] Refactor: use central "render_markdown" method --- src/pretalx/mail/models.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/pretalx/mail/models.py b/src/pretalx/mail/models.py index 88eed5516..68e318153 100644 --- a/src/pretalx/mail/models.py +++ b/src/pretalx/mail/models.py @@ -1,7 +1,5 @@ from copy import deepcopy -import bleach -import markdown from django.conf import settings from django.db import models, transaction from django.template.loader import get_template @@ -12,7 +10,7 @@ from pretalx.common.exceptions import SendMailException from pretalx.common.mixins.models import PretalxModel -from pretalx.common.templatetags.rich_text import ALLOWED_TAGS +from pretalx.common.templatetags.rich_text import render_markdown from pretalx.common.urls import EventUrls from pretalx.mail.context import get_mail_context from pretalx.mail.signals import queuedmail_post_send @@ -248,10 +246,7 @@ def make_html(self): sig = event.mail_settings["signature"] if sig.strip().startswith("-- "): sig = sig.strip()[3:].strip() - body_md = bleach.linkify( - bleach.clean(markdown.markdown(self.text), tags=ALLOWED_TAGS), - parse_email=True, - ) + body_md = render_markdown(self.text) html_context = { "body": body_md, "event": event, From 8ad8a3246a574132f4cdcd1f1e22810f841865da Mon Sep 17 00:00:00 2001 From: lcduong Date: Thu, 3 Oct 2024 14:45:35 +0700 Subject: [PATCH 036/195] optimize import --- src/pretalx/agenda/views/schedule.py | 3 +-- src/pretalx/agenda/views/talk.py | 2 -- src/pretalx/cfp/flow.py | 2 -- src/pretalx/cfp/views/locale.py | 2 -- 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/pretalx/agenda/views/schedule.py b/src/pretalx/agenda/views/schedule.py index e79068bb6..19644484a 100644 --- a/src/pretalx/agenda/views/schedule.py +++ b/src/pretalx/agenda/views/schedule.py @@ -20,9 +20,8 @@ from django_context_decorator import context from pretalx.common.mixins.views import EventPermissionRequired -from pretalx.common.signals import register_data_exporters, register_my_data_exporters -from pretalx.common.utils import safe_filename from pretalx.common.signals import register_data_exporters +from pretalx.common.signals import register_my_data_exporters from pretalx.common.text.path import safe_filename from pretalx.schedule.ascii import draw_ascii_schedule from pretalx.schedule.exporters import ScheduleData diff --git a/src/pretalx/agenda/views/talk.py b/src/pretalx/agenda/views/talk.py index bbb2a7da4..fbe49d135 100644 --- a/src/pretalx/agenda/views/talk.py +++ b/src/pretalx/agenda/views/talk.py @@ -19,8 +19,6 @@ from pretalx.cfp.views.event import EventPageMixin from pretalx.common.mixins.views import ( EventPermissionRequired, - PermissionRequired, - SocialMediaCardMixin, ) from pretalx.common.mixins.views import PermissionRequired, SocialMediaCardMixin from pretalx.common.text.phrases import phrases diff --git a/src/pretalx/cfp/flow.py b/src/pretalx/cfp/flow.py index 0bc73aad5..5c3cb2e95 100644 --- a/src/pretalx/cfp/flow.py +++ b/src/pretalx/cfp/flow.py @@ -24,8 +24,6 @@ from pretalx.cfp.signals import cfp_steps from pretalx.common.exceptions import SendMailException -from pretalx.common.text.phrases import phrases -from pretalx.common.utils import language from pretalx.common.language import language from pretalx.common.text.phrases import phrases from pretalx.person.forms import SpeakerProfileForm, UserForm diff --git a/src/pretalx/cfp/views/locale.py b/src/pretalx/cfp/views/locale.py index cc6d6456a..75ca8e2d7 100644 --- a/src/pretalx/cfp/views/locale.py +++ b/src/pretalx/cfp/views/locale.py @@ -9,8 +9,6 @@ from django.utils.translation import override from django.views.generic import View -from pretalx.common.text.phrases import phrases - class LocaleSet(View): def get(self, request, *args, **kwargs): From a8c827ec13ee81597f24ccd0f4f5997f8f0a2750 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 30 Apr 2024 13:51:30 +0200 Subject: [PATCH 037/195] Refactor pretalx.common.mixins --- src/pretalx/agenda/views/featured.py | 2 +- src/pretalx/agenda/views/schedule.py | 2 +- src/pretalx/agenda/views/speaker.py | 4 +- src/pretalx/agenda/views/talk.py | 6 +- src/pretalx/cfp/views/event.py | 2 +- .../{mixins/forms.py => forms/mixins.py} | 0 src/pretalx/common/mixins/__init__.py | 0 src/pretalx/common/mixins/models.py | 176 ------------------ src/pretalx/common/models/mixins.py | 130 +++++++++++++ src/pretalx/common/views/__init__.py | 23 +++ src/pretalx/common/views/cache.py | 32 ++++ src/pretalx/common/views/generic.py | 168 +++++++++++++++++ src/pretalx/common/views/helpers.py | 15 ++ .../{mixins/views.py => views/mixins.py} | 0 src/pretalx/event/forms.py | 2 +- src/pretalx/event/migrations/0001_initial.py | 4 +- .../migrations/0013_auto_20180407_0817.py | 6 +- src/pretalx/event/models/event.py | 2 +- src/pretalx/event/models/organiser.py | 2 +- src/pretalx/mail/migrations/0001_initial.py | 6 +- src/pretalx/mail/models.py | 2 +- src/pretalx/orga/forms/cfp.py | 2 +- src/pretalx/orga/forms/event.py | 2 +- src/pretalx/orga/forms/mails.py | 2 +- src/pretalx/orga/forms/review.py | 2 +- src/pretalx/orga/forms/schedule.py | 2 +- src/pretalx/orga/forms/submission.py | 2 +- src/pretalx/orga/views/admin.py | 4 +- src/pretalx/orga/views/cards.py | 2 +- src/pretalx/orga/views/cfp.py | 7 +- src/pretalx/orga/views/dashboard.py | 2 +- src/pretalx/orga/views/event.py | 11 +- src/pretalx/orga/views/mails.py | 6 +- src/pretalx/orga/views/organiser.py | 5 +- src/pretalx/orga/views/plugins.py | 2 +- src/pretalx/orga/views/review.py | 2 +- src/pretalx/orga/views/schedule.py | 9 +- src/pretalx/orga/views/speaker.py | 6 +- src/pretalx/orga/views/submission.py | 8 +- src/pretalx/person/forms.py | 4 +- src/pretalx/person/migrations/0001_initial.py | 6 +- .../0008_data_populate_user_code.py | 2 +- .../migrations/0014_speakerinformation.py | 4 +- src/pretalx/person/models/information.py | 2 +- src/pretalx/person/models/profile.py | 2 +- src/pretalx/person/models/user.py | 2 +- src/pretalx/schedule/forms.py | 2 +- .../schedule/migrations/0001_initial.py | 10 +- src/pretalx/schedule/models/availability.py | 2 +- src/pretalx/schedule/models/room.py | 2 +- src/pretalx/schedule/models/schedule.py | 2 +- src/pretalx/schedule/models/slot.py | 2 +- src/pretalx/submission/forms/feedback.py | 2 +- src/pretalx/submission/forms/question.py | 2 +- src/pretalx/submission/forms/submission.py | 4 +- src/pretalx/submission/forms/tag.py | 2 +- .../submission/migrations/0001_initial.py | 16 +- .../migrations/0003_auto_20170830_1813.py | 4 +- .../submission/migrations/0014_resource.py | 4 +- .../migrations/0041_auto_20191105_0042.py | 6 +- .../migrations/0052_auto_20201010_1307.py | 4 +- src/pretalx/submission/models/access_code.py | 2 +- src/pretalx/submission/models/cfp.py | 2 +- src/pretalx/submission/models/feedback.py | 2 +- src/pretalx/submission/models/question.py | 2 +- src/pretalx/submission/models/resource.py | 2 +- src/pretalx/submission/models/review.py | 2 +- src/pretalx/submission/models/submission.py | 2 +- src/pretalx/submission/models/tag.py | 2 +- src/pretalx/submission/models/track.py | 2 +- src/pretalx/submission/models/type.py | 2 +- src/tests/common/test_common_utils.py | 6 +- src/tests/submission/test_submission_model.py | 2 +- 73 files changed, 484 insertions(+), 290 deletions(-) rename src/pretalx/common/{mixins/forms.py => forms/mixins.py} (100%) delete mode 100644 src/pretalx/common/mixins/__init__.py delete mode 100644 src/pretalx/common/mixins/models.py create mode 100644 src/pretalx/common/views/__init__.py create mode 100644 src/pretalx/common/views/cache.py create mode 100644 src/pretalx/common/views/generic.py create mode 100644 src/pretalx/common/views/helpers.py rename src/pretalx/common/{mixins/views.py => views/mixins.py} (100%) diff --git a/src/pretalx/agenda/views/featured.py b/src/pretalx/agenda/views/featured.py index 21d6792fb..18debc72a 100644 --- a/src/pretalx/agenda/views/featured.py +++ b/src/pretalx/agenda/views/featured.py @@ -4,7 +4,7 @@ from django.views.generic import TemplateView from django_context_decorator import context -from pretalx.common.mixins.views import EventPermissionRequired +from pretalx.common.views.mixins import EventPermissionRequired from pretalx.submission.models.submission import SubmissionStates diff --git a/src/pretalx/agenda/views/schedule.py b/src/pretalx/agenda/views/schedule.py index 19644484a..f5297aff0 100644 --- a/src/pretalx/agenda/views/schedule.py +++ b/src/pretalx/agenda/views/schedule.py @@ -19,10 +19,10 @@ from django.views.generic import TemplateView from django_context_decorator import context -from pretalx.common.mixins.views import EventPermissionRequired from pretalx.common.signals import register_data_exporters from pretalx.common.signals import register_my_data_exporters from pretalx.common.text.path import safe_filename +from pretalx.common.views.mixins import EventPermissionRequired from pretalx.schedule.ascii import draw_ascii_schedule from pretalx.schedule.exporters import ScheduleData from pretalx.submission.models.submission import SubmissionFavourite diff --git a/src/pretalx/agenda/views/speaker.py b/src/pretalx/agenda/views/speaker.py index 03ba87e13..8b9a89de7 100644 --- a/src/pretalx/agenda/views/speaker.py +++ b/src/pretalx/agenda/views/speaker.py @@ -13,13 +13,13 @@ from django.views.generic import DetailView, ListView, TemplateView from django_context_decorator import context -from pretalx.common.mixins.views import ( +from pretalx.common.text.path import safe_filename +from pretalx.common.views.mixins import ( EventPermissionRequired, Filterable, PermissionRequired, SocialMediaCardMixin, ) -from pretalx.common.text.path import safe_filename from pretalx.person.models import SpeakerProfile, User from pretalx.submission.models import QuestionTarget diff --git a/src/pretalx/agenda/views/talk.py b/src/pretalx/agenda/views/talk.py index fbe49d135..18ffead52 100644 --- a/src/pretalx/agenda/views/talk.py +++ b/src/pretalx/agenda/views/talk.py @@ -17,11 +17,9 @@ from pretalx.agenda.signals import register_recording_provider from pretalx.cfp.views.event import EventPageMixin -from pretalx.common.mixins.views import ( - EventPermissionRequired, -) -from pretalx.common.mixins.views import PermissionRequired, SocialMediaCardMixin from pretalx.common.text.phrases import phrases +from pretalx.common.views.mixins import PermissionRequired, SocialMediaCardMixin, \ + EventPermissionRequired from pretalx.schedule.models import Schedule, TalkSlot from pretalx.submission.forms import FeedbackForm from pretalx.submission.models import QuestionTarget, Submission, SubmissionStates diff --git a/src/pretalx/cfp/views/event.py b/src/pretalx/cfp/views/event.py index 702226e7d..2e5fc0dd4 100644 --- a/src/pretalx/cfp/views/event.py +++ b/src/pretalx/cfp/views/event.py @@ -7,7 +7,7 @@ from django.views.generic import TemplateView from django_context_decorator import context -from pretalx.common.mixins.views import PermissionRequired +from pretalx.common.views.mixins import PermissionRequired from pretalx.event.models import Event diff --git a/src/pretalx/common/mixins/forms.py b/src/pretalx/common/forms/mixins.py similarity index 100% rename from src/pretalx/common/mixins/forms.py rename to src/pretalx/common/forms/mixins.py diff --git a/src/pretalx/common/mixins/__init__.py b/src/pretalx/common/mixins/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/pretalx/common/mixins/models.py b/src/pretalx/common/mixins/models.py deleted file mode 100644 index f6cc0e92e..000000000 --- a/src/pretalx/common/mixins/models.py +++ /dev/null @@ -1,176 +0,0 @@ -import json -from contextlib import suppress - -from django.contrib.contenttypes.models import ContentType -from django.db import models -from django.utils.crypto import get_random_string -from django_scopes import ScopedManager, scopes_disabled -from i18nfield.utils import I18nJSONEncoder - -SENSITIVE_KEYS = ["password", "secret", "api_key"] - - -class TimestampedModel(models.Model): - created = models.DateTimeField(auto_now_add=True, blank=True, null=True) - updated = models.DateTimeField(auto_now=True, blank=True, null=True) - - class Meta: - abstract = True - - -class LogMixin: - def log_action(self, action, data=None, person=None, orga=False): - if not self.pk: - return - - from pretalx.common.models import ActivityLog - - if data and isinstance(data, dict): - for key, value in data.items(): - if any(sensitive_key in key for sensitive_key in SENSITIVE_KEYS): - value = data[key] - data[key] = "********" if value else value - data = json.dumps(data, cls=I18nJSONEncoder) - elif data: - raise TypeError( - f"Logged data should always be a dictionary, not {type(data)}." - ) - - return ActivityLog.objects.create( - event=getattr(self, "event", None), - person=person, - content_object=self, - action_type=action, - data=data, - is_orga_action=orga, - ) - - def logged_actions(self): - from pretalx.common.models import ActivityLog - - return ( - ActivityLog.objects.filter( - content_type=ContentType.objects.get_for_model(type(self)), - object_id=self.pk, - ) - .select_related("event", "person") - .prefetch_related("content_object") - ) - - -class FileCleanupMixin: - """Deletes all uploaded files when object is deleted.""" - - def _delete_files(self): - file_attributes = [ - field.name - for field in self._meta.fields - if isinstance(field, models.FileField) - ] - for field in file_attributes: - value = getattr(self, field, None) - if value: - with suppress(Exception): - value.delete(save=False) - - def delete(self, *args, **kwargs): - self._delete_files() - return super().delete(*args, **kwargs) - - -class PretalxModel(LogMixin, TimestampedModel, FileCleanupMixin, models.Model): - """ - Base model for most pretalx models. Suitable for plugins. - """ - - objects = ScopedManager(event="event") - - class Meta: - abstract = True - - -class GenerateCode: - """Generates a random code on first save. - - Omits some character pairs because they are hard to - read/differentiate: 1/I, O/0, 2/Z, 4/A, 5/S, 6/G. - """ - - _code_length = 6 - _code_charset = list("ABCDEFGHJKLMNPQRSTUVWXYZ3789") - _code_property = "code" - - @classmethod - def generate_code(cls, length=None): - length = length or cls._code_length - return get_random_string(length=length, allowed_chars=cls._code_charset) - - def assign_code(self, length=None): - length = length or self._code_length - while True: - code = self.generate_code(length=length) - with scopes_disabled(): - if not self.__class__.objects.filter( - **{f"{self._code_property}__iexact": code} - ).exists(): - setattr(self, self._code_property, code) - return - - def save(self, *args, **kwargs): - if not getattr(self, self._code_property, None): - self.assign_code() - return super().save(*args, **kwargs) - - -class OrderedModel: - """Provides methods to move a model up and down in a queryset. - - Used with OrderModelView to provide a view to move models up and down. - Implement the `get_order_queryset` method as a classmethod or staticmethod - to provide the queryset to order. - """ - - order_field = "position" - order_up_url = "urls.up" - order_down_url = "urls.down" - - @staticmethod - def get_order_queryset(**kwargs): - raise NotImplementedError - - def _get_attribute(self, attribute): - result = self - for part in attribute.split("."): - result = getattr(result, part) - return result - - def get_down_url(self): - return self._get_attribute(self.order_down_url) - - def get_up_url(self): - return self._get_attribute(self.order_up_url) - - def up(self): - return self._move(up=True) - - def down(self): - return self._move(up=False) - - @property - def order_queryset(self): - return self.get_order_queryset(event=self.event) - - def move(self, up=True): - queryset = list(self.order_queryset.order_by(self.order_field)) - index = queryset.index(self) - if index != 0 and up: - queryset[index - 1], queryset[index] = queryset[index], queryset[index - 1] - elif index != len(queryset) - 1 and not up: - queryset[index + 1], queryset[index] = queryset[index], queryset[index + 1] - - for index, element in enumerate(queryset): - if element.position != index: - element.position = index - element.save() - - move.alters_data = True diff --git a/src/pretalx/common/models/mixins.py b/src/pretalx/common/models/mixins.py index a82b69650..f6cc0e92e 100644 --- a/src/pretalx/common/models/mixins.py +++ b/src/pretalx/common/models/mixins.py @@ -1,11 +1,23 @@ import json +from contextlib import suppress from django.contrib.contenttypes.models import ContentType +from django.db import models +from django.utils.crypto import get_random_string +from django_scopes import ScopedManager, scopes_disabled from i18nfield.utils import I18nJSONEncoder SENSITIVE_KEYS = ["password", "secret", "api_key"] +class TimestampedModel(models.Model): + created = models.DateTimeField(auto_now_add=True, blank=True, null=True) + updated = models.DateTimeField(auto_now=True, blank=True, null=True) + + class Meta: + abstract = True + + class LogMixin: def log_action(self, action, data=None, person=None, orga=False): if not self.pk: @@ -44,3 +56,121 @@ def logged_actions(self): .select_related("event", "person") .prefetch_related("content_object") ) + + +class FileCleanupMixin: + """Deletes all uploaded files when object is deleted.""" + + def _delete_files(self): + file_attributes = [ + field.name + for field in self._meta.fields + if isinstance(field, models.FileField) + ] + for field in file_attributes: + value = getattr(self, field, None) + if value: + with suppress(Exception): + value.delete(save=False) + + def delete(self, *args, **kwargs): + self._delete_files() + return super().delete(*args, **kwargs) + + +class PretalxModel(LogMixin, TimestampedModel, FileCleanupMixin, models.Model): + """ + Base model for most pretalx models. Suitable for plugins. + """ + + objects = ScopedManager(event="event") + + class Meta: + abstract = True + + +class GenerateCode: + """Generates a random code on first save. + + Omits some character pairs because they are hard to + read/differentiate: 1/I, O/0, 2/Z, 4/A, 5/S, 6/G. + """ + + _code_length = 6 + _code_charset = list("ABCDEFGHJKLMNPQRSTUVWXYZ3789") + _code_property = "code" + + @classmethod + def generate_code(cls, length=None): + length = length or cls._code_length + return get_random_string(length=length, allowed_chars=cls._code_charset) + + def assign_code(self, length=None): + length = length or self._code_length + while True: + code = self.generate_code(length=length) + with scopes_disabled(): + if not self.__class__.objects.filter( + **{f"{self._code_property}__iexact": code} + ).exists(): + setattr(self, self._code_property, code) + return + + def save(self, *args, **kwargs): + if not getattr(self, self._code_property, None): + self.assign_code() + return super().save(*args, **kwargs) + + +class OrderedModel: + """Provides methods to move a model up and down in a queryset. + + Used with OrderModelView to provide a view to move models up and down. + Implement the `get_order_queryset` method as a classmethod or staticmethod + to provide the queryset to order. + """ + + order_field = "position" + order_up_url = "urls.up" + order_down_url = "urls.down" + + @staticmethod + def get_order_queryset(**kwargs): + raise NotImplementedError + + def _get_attribute(self, attribute): + result = self + for part in attribute.split("."): + result = getattr(result, part) + return result + + def get_down_url(self): + return self._get_attribute(self.order_down_url) + + def get_up_url(self): + return self._get_attribute(self.order_up_url) + + def up(self): + return self._move(up=True) + + def down(self): + return self._move(up=False) + + @property + def order_queryset(self): + return self.get_order_queryset(event=self.event) + + def move(self, up=True): + queryset = list(self.order_queryset.order_by(self.order_field)) + index = queryset.index(self) + if index != 0 and up: + queryset[index - 1], queryset[index] = queryset[index], queryset[index - 1] + elif index != len(queryset) - 1 and not up: + queryset[index + 1], queryset[index] = queryset[index], queryset[index + 1] + + for index, element in enumerate(queryset): + if element.position != index: + element.position = index + element.save() + + move.alters_data = True diff --git a/src/pretalx/common/views/__init__.py b/src/pretalx/common/views/__init__.py new file mode 100644 index 000000000..71d916980 --- /dev/null +++ b/src/pretalx/common/views/__init__.py @@ -0,0 +1,23 @@ +from .cache import conditional_cache_page +from .errors import error_view, handle_500 +from .generic import ( + CreateOrUpdateView, + EventSocialMediaCard, + GenericLoginView, + GenericResetView, + OrderedModelView, +) +from .helpers import get_static, is_form_bound + +__all__ = [ + "CreateOrUpdateView", + "EventSocialMediaCard", + "GenericLoginView", + "GenericResetView", + "OrderedModelView", + "conditional_cache_page", + "error_view", + "get_static", + "handle_500", + "is_form_bound", +] diff --git a/src/pretalx/common/views/cache.py b/src/pretalx/common/views/cache.py new file mode 100644 index 000000000..375974697 --- /dev/null +++ b/src/pretalx/common/views/cache.py @@ -0,0 +1,32 @@ +from django.views.decorators.cache import cache_page + + +def conditional_cache_page( + timeout, condition, *, cache=None, key_prefix=None, cache_control=None +): + """This decorator is exactly like cache_page, but with the option to skip + the caching entirely. + + The second argument is a callable, ``condition``. It's given the + request and all further arguments, and if it evaluates to a true-ish + value, the cache is used. + """ + + def decorator(func): + def wrapper(request, *args, **kwargs): + if condition(request, *args, **kwargs): + prefix = key_prefix + if callable(prefix): + prefix = prefix(request, *args, **kwargs) + response = cache_page(timeout=timeout, cache=cache, key_prefix=prefix)( + func + )(request, *args, **kwargs) + if cache_control and not cache_control(request, *args, **kwargs): + response.headers.pop("Expires") + response.headers.pop("Cache-Control") + + return func(request, *args, **kwargs) + + return wrapper + + return decorator diff --git a/src/pretalx/common/views/generic.py b/src/pretalx/common/views/generic.py new file mode 100644 index 000000000..cbd2e648a --- /dev/null +++ b/src/pretalx/common/views/generic.py @@ -0,0 +1,168 @@ +import urllib +from contextlib import suppress + +from django.contrib import messages +from django.contrib.auth import login +from django.http import Http404 +from django.shortcuts import get_object_or_404, redirect +from django.urls import NoReverseMatch, path +from django.utils.http import url_has_allowed_host_and_scheme +from django.utils.timezone import now +from django.utils.translation import gettext_lazy as _ +from django.views.generic import FormView, View +from django.views.generic.detail import SingleObjectTemplateResponseMixin +from django.views.generic.edit import ModelFormMixin, ProcessFormView +from django_context_decorator import context + +from pretalx.cfp.forms.auth import ResetForm +from pretalx.common.exceptions import SendMailException +from pretalx.common.text.phrases import phrases +from pretalx.common.views.mixins import SocialMediaCardMixin +from pretalx.person.forms import UserForm +from pretalx.person.models import User + + +class CreateOrUpdateView( + SingleObjectTemplateResponseMixin, ModelFormMixin, ProcessFormView +): + def set_object(self): + with suppress(self.model.DoesNotExist, AttributeError): + self.object = self.get_object() + + def get(self, request, *args, **kwargs): + self.set_object() + return super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + self.set_object() + return super().post(request, *args, **kwargs) + + +class GenericLoginView(FormView): + form_class = UserForm + + @context + def password_reset_link(self): + return self.get_password_reset_link() + + def dispatch(self, request, *args, **kwargs): + if not self.request.user.is_anonymous: + try: + return redirect(self.get_success_url()) + except Exception: + return redirect(self.success_url) + return super().dispatch(request, *args, **kwargs) + + @classmethod + def get_next_url_or_fallback(cls, request, fallback, ignore_next=False): + """Reused in logout()""" + params = request.GET.copy() + url = None if ignore_next else urllib.parse.unquote(params.pop("next", [""])[0]) + params = "?" + params.urlencode() if params else "" + if url and url_has_allowed_host_and_scheme(url, allowed_hosts=None): + return url + params + return fallback + params + + def get_success_url(self, ignore_next=False): + return self.get_next_url_or_fallback( + self.request, self.success_url, ignore_next=ignore_next + ) + + def get_redirect(self): + try: + return redirect(self.get_success_url()) + except NoReverseMatch: + return redirect(self.get_success_url(ignore_next=True)) + + def form_valid(self, form): + pk = form.save() + user = User.objects.filter(pk=pk).first() + login(self.request, user, backend="django.contrib.auth.backends.ModelBackend") + return self.get_redirect() + + +class GenericResetView(FormView): + form_class = ResetForm + + def form_valid(self, form): + user = form.cleaned_data["user"] + + if not user or ( + user.pw_reset_time + and (now() - user.pw_reset_time).total_seconds() < 3600 * 24 + ): + messages.success(self.request, phrases.cfp.auth_password_reset) + return redirect(self.get_success_url()) + + try: + user.reset_password( + event=getattr(self.request, "event", None), + orga="orga" in self.request.resolver_match.namespaces, + ) + except SendMailException: # pragma: no cover + messages.error(self.request, phrases.base.error_sending_mail) + return self.get(self.request, *self.args, **self.kwargs) + + messages.success(self.request, phrases.cfp.auth_password_reset) + user.log_action("pretalx.user.password.reset") + + return redirect(self.get_success_url()) + + +class OrderModelView(View): + """ + Use with OrderedModels to provide up and down links in the list view. + + You need to implement/override + - model + - permission_required + - get_success_url + + In urls.py, use MyOrderModelView.get_urls() to get the urls for this view. + """ + + model = None + permission_required = None + direction_up = True + + def __init__(self, *args, direction_up=True, **kwargs): + super().__init__(*args, **kwargs) + self.direction_up = direction_up + + def get_queryset(self): + return self.model.get_order_queryset(event=self.request.event) + + def dispatch(self, request, *args, direction=None, pk=None, **kwargs): + obj = get_object_or_404(self.get_queryset(), pk=pk) + if not request.user.has_perm(self.permission_required, obj): + messages.error( + request, _("Sorry, you are not allowed to reorder this list.") + ) + raise Http404() + obj.move(up=self.direction_up) + messages.success(request, _("The order has been updated.")) + return redirect(self.get_success_url()) + + def get_success_url(self): + return self.request.event.orga_urls.base + + @classmethod + def get_urls(cls, base_url=""): + # Return the two patterns for moving up and down + url_name = f"settings.{cls.model._meta.app_label}.{cls.model._meta.model_name}." + return [ + path( + f"{base_url}up", + cls.as_view(direction_up=True), + name=f"{url_name}.up", + ), + path( + f"{base_url}down", + cls.as_view(direction_up=False), + name=f"{url_name}.down", + ), + ] + + +class EventSocialMediaCard(SocialMediaCardMixin, View): + pass diff --git a/src/pretalx/common/views/helpers.py b/src/pretalx/common/views/helpers.py new file mode 100644 index 000000000..db723953c --- /dev/null +++ b/src/pretalx/common/views/helpers.py @@ -0,0 +1,15 @@ +from django.conf import settings +from django.http import FileResponse, Http404 + + +def is_form_bound(request, form_name, form_param="form"): + return request.method == "POST" and request.POST.get(form_param) == form_name + + +def get_static(request, path, content_type): # pragma: no cover + path = settings.BASE_DIR / "pretalx/static" / path + if not path.exists(): + raise Http404() + return FileResponse( + open(path, "rb"), content_type=content_type, as_attachment=False + ) diff --git a/src/pretalx/common/mixins/views.py b/src/pretalx/common/views/mixins.py similarity index 100% rename from src/pretalx/common/mixins/views.py rename to src/pretalx/common/views/mixins.py diff --git a/src/pretalx/event/forms.py b/src/pretalx/event/forms.py index 9803c1837..d5623056e 100644 --- a/src/pretalx/event/forms.py +++ b/src/pretalx/event/forms.py @@ -8,7 +8,7 @@ from i18nfield.forms import I18nModelForm from pretalx.common.forms.fields import ImageField -from pretalx.common.mixins.forms import I18nHelpText, ReadOnlyFlag +from pretalx.common.forms.mixins import I18nHelpText, ReadOnlyFlag from pretalx.event.models import Event, Organiser, Team, TeamInvite from pretalx.orga.forms.widgets import HeaderSelect, MultipleLanguagesWidget from pretalx.submission.models import Track diff --git a/src/pretalx/event/migrations/0001_initial.py b/src/pretalx/event/migrations/0001_initial.py index 86660e782..335f788e4 100644 --- a/src/pretalx/event/migrations/0001_initial.py +++ b/src/pretalx/event/migrations/0001_initial.py @@ -4,7 +4,7 @@ from django.db import migrations, models import django.db.models.deletion import i18nfield.fields -import pretalx.common.mixins +import pretalx.common.models.mixins class Migration(migrations.Migration): @@ -49,7 +49,7 @@ class Migration(migrations.Migration): ("locale_array", models.TextField(default="en")), ("locale", models.CharField(default="en", max_length=32)), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="Event_SettingsStore", diff --git a/src/pretalx/event/migrations/0013_auto_20180407_0817.py b/src/pretalx/event/migrations/0013_auto_20180407_0817.py index 8f5c0a5a4..90291f742 100644 --- a/src/pretalx/event/migrations/0013_auto_20180407_0817.py +++ b/src/pretalx/event/migrations/0013_auto_20180407_0817.py @@ -5,7 +5,7 @@ from django.db import migrations, models import django.db.models.deletion import i18nfield.fields -import pretalx.common.mixins.models +import pretalx.common.models.mixins import pretalx.event.models.organiser @@ -39,7 +39,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="Team", @@ -75,7 +75,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="TeamInvite", diff --git a/src/pretalx/event/models/event.py b/src/pretalx/event/models/event.py index 7c6af26bd..43e838ea3 100644 --- a/src/pretalx/event/models/event.py +++ b/src/pretalx/event/models/event.py @@ -18,8 +18,8 @@ from pretalx.common.cache import ObjectRelatedCache from pretalx.common.language import LANGUAGE_NAMES -from pretalx.common.mixins.models import PretalxModel from pretalx.common.models import TIMEZONE_CHOICES +from pretalx.common.models.mixins import PretalxModel from pretalx.common.models.settings import hierarkey from pretalx.common.plugins import get_all_plugins from pretalx.common.text.daterange import daterange diff --git a/src/pretalx/event/models/organiser.py b/src/pretalx/event/models/organiser.py index 1fcf75170..25918cc12 100644 --- a/src/pretalx/event/models/organiser.py +++ b/src/pretalx/event/models/organiser.py @@ -11,7 +11,7 @@ from django_scopes import scope from i18nfield.fields import I18nCharField -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.urls import EventUrls, build_absolute_uri from pretalx.event.models.event import FULL_SLUG_REGEX from pretalx.person.models import User diff --git a/src/pretalx/mail/migrations/0001_initial.py b/src/pretalx/mail/migrations/0001_initial.py index 3311c5fdc..a14bc9732 100644 --- a/src/pretalx/mail/migrations/0001_initial.py +++ b/src/pretalx/mail/migrations/0001_initial.py @@ -3,7 +3,7 @@ from django.db import migrations, models import django.db.models.deletion import i18nfield.fields -import pretalx.common.mixins +import pretalx.common.models.mixins class Migration(migrations.Migration): @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="QueuedMail", @@ -62,6 +62,6 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), ] diff --git a/src/pretalx/mail/models.py b/src/pretalx/mail/models.py index 68e318153..b29737f2a 100644 --- a/src/pretalx/mail/models.py +++ b/src/pretalx/mail/models.py @@ -9,7 +9,7 @@ from i18nfield.fields import I18nCharField, I18nTextField from pretalx.common.exceptions import SendMailException -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.templatetags.rich_text import render_markdown from pretalx.common.urls import EventUrls from pretalx.mail.context import get_mail_context diff --git a/src/pretalx/orga/forms/cfp.py b/src/pretalx/orga/forms/cfp.py index 1f960eae6..af9a6aafb 100644 --- a/src/pretalx/orga/forms/cfp.py +++ b/src/pretalx/orga/forms/cfp.py @@ -9,7 +9,7 @@ from i18nfield.forms import I18nFormMixin, I18nModelForm from i18nfield.strings import LazyI18nString -from pretalx.common.mixins.forms import I18nHelpText, JsonSubfieldMixin, ReadOnlyFlag +from pretalx.common.forms.mixins import I18nHelpText, JsonSubfieldMixin, ReadOnlyFlag from pretalx.submission.models import ( AnswerOption, Question, diff --git a/src/pretalx/orga/forms/event.py b/src/pretalx/orga/forms/event.py index 1e29f886d..79a28a2e5 100644 --- a/src/pretalx/orga/forms/event.py +++ b/src/pretalx/orga/forms/event.py @@ -15,7 +15,7 @@ from i18nfield.forms import I18nFormMixin, I18nModelForm from pretalx.common.forms.fields import ImageField -from pretalx.common.mixins.forms import ( +from pretalx.common.forms.mixins import ( HierarkeyMixin, I18nHelpText, JsonSubfieldMixin, diff --git a/src/pretalx/orga/forms/mails.py b/src/pretalx/orga/forms/mails.py index 894c419b3..8ea8de2b5 100644 --- a/src/pretalx/orga/forms/mails.py +++ b/src/pretalx/orga/forms/mails.py @@ -10,8 +10,8 @@ from i18nfield.forms import I18nModelForm from pretalx.common.exceptions import SendMailException +from pretalx.common.forms.mixins import I18nHelpText, ReadOnlyFlag from pretalx.common.language import language -from pretalx.common.mixins.forms import I18nHelpText, ReadOnlyFlag from pretalx.common.templatetags.rich_text import rich_text from pretalx.mail.context import get_available_placeholders from pretalx.mail.models import MailTemplate, QueuedMail diff --git a/src/pretalx/orga/forms/review.py b/src/pretalx/orga/forms/review.py index eaf49d073..0823829f2 100644 --- a/src/pretalx/orga/forms/review.py +++ b/src/pretalx/orga/forms/review.py @@ -8,8 +8,8 @@ from django.utils.translation import ngettext as _n from django_scopes.forms import SafeModelMultipleChoiceField +from pretalx.common.forms.mixins import ReadOnlyFlag from pretalx.common.forms.widgets import MarkdownWidget -from pretalx.common.mixins.forms import ReadOnlyFlag from pretalx.common.text.phrases import phrases from pretalx.orga.forms.export import ExportForm from pretalx.person.models import User diff --git a/src/pretalx/orga/forms/schedule.py b/src/pretalx/orga/forms/schedule.py index d8fc50a32..f214bf326 100644 --- a/src/pretalx/orga/forms/schedule.py +++ b/src/pretalx/orga/forms/schedule.py @@ -4,7 +4,7 @@ from django_scopes.forms import SafeModelMultipleChoiceField from i18nfield.forms import I18nFormMixin, I18nModelForm -from pretalx.common.mixins.forms import I18nHelpText +from pretalx.common.forms.mixins import I18nHelpText from pretalx.orga.forms.export import ExportForm from pretalx.schedule.models import Room, Schedule from pretalx.submission.models.submission import Submission, SubmissionStates diff --git a/src/pretalx/orga/forms/submission.py b/src/pretalx/orga/forms/submission.py index 0fdf6ea28..6b11e2b0b 100644 --- a/src/pretalx/orga/forms/submission.py +++ b/src/pretalx/orga/forms/submission.py @@ -6,8 +6,8 @@ from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField from pretalx.common.forms.fields import ImageField +from pretalx.common.forms.mixins import ReadOnlyFlag, RequestRequire from pretalx.common.forms.widgets import MarkdownWidget -from pretalx.common.mixins.forms import ReadOnlyFlag, RequestRequire from pretalx.submission.models import Submission, SubmissionStates, SubmissionType diff --git a/src/pretalx/orga/views/admin.py b/src/pretalx/orga/views/admin.py index 1575cc4c8..c4b0339da 100644 --- a/src/pretalx/orga/views/admin.py +++ b/src/pretalx/orga/views/admin.py @@ -7,9 +7,7 @@ from django.shortcuts import redirect from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from django.views.generic import DetailView, FormView, ListView, TemplateView from django.views.generic import ( - DeleteView, DetailView, FormView, ListView, @@ -19,10 +17,10 @@ from django_scopes import scopes_disabled from pretalx.celery_app import app -from pretalx.common.mixins.views import ActionConfirmMixin, PermissionRequired from pretalx.common.models.settings import GlobalSettings from pretalx.common.text.phrases import phrases from pretalx.common.update_check import check_result_table, update_check +from pretalx.common.views.mixins import PermissionRequired, ActionConfirmMixin from pretalx.orga.forms.admin import UpdateSettingsForm from pretalx.person.models import User diff --git a/src/pretalx/orga/views/cards.py b/src/pretalx/orga/views/cards.py index 05b085227..ccc1dc0be 100644 --- a/src/pretalx/orga/views/cards.py +++ b/src/pretalx/orga/views/cards.py @@ -20,7 +20,7 @@ from reportlab.pdfbase.ttfonts import TTFont from reportlab.platypus import BaseDocTemplate, Flowable, Frame, PageTemplate, Paragraph -from pretalx.common.mixins.views import EventPermissionRequired +from pretalx.common.views.mixins import EventPermissionRequired from pretalx.submission.models import SubmissionStates reportlab.rl_config.TTFSearchPath.append(finders.find("fonts")) diff --git a/src/pretalx/orga/views/cfp.py b/src/pretalx/orga/views/cfp.py index ae3b330fb..b61567e75 100644 --- a/src/pretalx/orga/views/cfp.py +++ b/src/pretalx/orga/views/cfp.py @@ -17,14 +17,15 @@ from pretalx.cfp.flow import CfPFlow from pretalx.common.forms import I18nFormSet -from pretalx.common.mixins.views import ( +from pretalx.common.text.serialize import I18nStrJSONEncoder +from pretalx.common.views import CreateOrUpdateView +from pretalx.common.views.generic import OrderModelView +from pretalx.common.views.mixins import ( ActionFromUrl, EventPermissionRequired, PaginationMixin, PermissionRequired, ) -from pretalx.common.text.serialize import I18nStrJSONEncoder -from pretalx.common.views import CreateOrUpdateView, OrderModelView from pretalx.orga.forms import CfPForm, QuestionForm, SubmissionTypeForm, TrackForm from pretalx.orga.forms.cfp import ( AccessCodeSendForm, diff --git a/src/pretalx/orga/views/dashboard.py b/src/pretalx/orga/views/dashboard.py index 5b20d0b0e..2962ef481 100644 --- a/src/pretalx/orga/views/dashboard.py +++ b/src/pretalx/orga/views/dashboard.py @@ -10,8 +10,8 @@ from django_context_decorator import context from django_scopes import scopes_disabled -from pretalx.common.mixins.views import EventPermissionRequired, PermissionRequired from pretalx.common.models.log import ActivityLog +from pretalx.common.views.mixins import EventPermissionRequired, PermissionRequired from pretalx.event.models import Event, Organiser from pretalx.event.stages import get_stages from pretalx.submission.models import Review, Submission, SubmissionStates diff --git a/src/pretalx/orga/views/event.py b/src/pretalx/orga/views/event.py index 10c26a260..554f386e4 100644 --- a/src/pretalx/orga/views/event.py +++ b/src/pretalx/orga/views/event.py @@ -30,16 +30,17 @@ from rest_framework.authtoken.models import Token from pretalx.common.forms import I18nEventFormSet, I18nFormSet -from pretalx.common.mixins.views import ( +from pretalx.common.models import ActivityLog +from pretalx.common.tasks import regenerate_css +from pretalx.common.templatetags.rich_text import render_markdown +from pretalx.common.views import is_form_bound +from pretalx.common.views.generic import OrderModelView +from pretalx.common.views.mixins import ( ActionFromUrl, EventPermissionRequired, PermissionRequired, SensibleBackWizardMixin, ) -from pretalx.common.models import ActivityLog -from pretalx.common.tasks import regenerate_css -from pretalx.common.templatetags.rich_text import render_markdown -from pretalx.common.views import OrderModelView, is_form_bound from pretalx.event.forms import ( EventWizardBasicsForm, EventWizardCopyForm, diff --git a/src/pretalx/orga/views/mails.py b/src/pretalx/orga/views/mails.py index 064d664cd..cd51b85ed 100644 --- a/src/pretalx/orga/views/mails.py +++ b/src/pretalx/orga/views/mails.py @@ -10,7 +10,9 @@ from pretalx.common.language import language from pretalx.common.mail import TolerantDict -from pretalx.common.mixins.views import ( +from pretalx.common.templatetags.rich_text import rich_text +from pretalx.common.views import CreateOrUpdateView +from pretalx.common.views.mixins import ( ActionFromUrl, EventPermissionRequired, Filterable, @@ -18,8 +20,6 @@ PermissionRequired, Sortable, ) -from pretalx.common.templatetags.rich_text import rich_text -from pretalx.common.views import CreateOrUpdateView from pretalx.mail.models import MailTemplate, QueuedMail from pretalx.orga.forms.mails import ( DraftRemindersForm, diff --git a/src/pretalx/orga/views/organiser.py b/src/pretalx/orga/views/organiser.py index bf24f5ef0..6af1e16d1 100644 --- a/src/pretalx/orga/views/organiser.py +++ b/src/pretalx/orga/views/organiser.py @@ -11,9 +11,10 @@ from django_context_decorator import context from pretalx.common.exceptions import SendMailException -from pretalx.common.mixins.views import PermissionRequired from pretalx.common.text.phrases import phrases -from pretalx.common.views import CreateOrUpdateView, is_form_bound +from pretalx.common.views import CreateOrUpdateView +from pretalx.common.views import is_form_bound +from pretalx.common.views.mixins import PermissionRequired from pretalx.event.forms import OrganiserForm, TeamForm, TeamInviteForm from pretalx.event.models import Organiser, Team, TeamInvite from pretalx.orga.forms.sso_client_form import SSOClientForm diff --git a/src/pretalx/orga/views/plugins.py b/src/pretalx/orga/views/plugins.py index 7bfe12999..2ccb242a5 100644 --- a/src/pretalx/orga/views/plugins.py +++ b/src/pretalx/orga/views/plugins.py @@ -6,8 +6,8 @@ from django.views.generic import TemplateView from django_context_decorator import context -from pretalx.common.mixins.views import EventPermissionRequired from pretalx.common.plugins import get_all_plugins_grouped +from pretalx.common.views.mixins import EventPermissionRequired class EventPluginsView(EventPermissionRequired, TemplateView): diff --git a/src/pretalx/orga/views/review.py b/src/pretalx/orga/views/review.py index 5c75d7dae..ade15ce82 100644 --- a/src/pretalx/orga/views/review.py +++ b/src/pretalx/orga/views/review.py @@ -11,8 +11,8 @@ from django.views.generic import FormView, TemplateView from django_context_decorator import context -from pretalx.common.mixins.views import EventPermissionRequired, PermissionRequired from pretalx.common.views import CreateOrUpdateView +from pretalx.common.views.mixins import EventPermissionRequired, PermissionRequired from pretalx.orga.forms.review import ( DirectionForm, ProposalForReviewerForm, diff --git a/src/pretalx/orga/views/schedule.py b/src/pretalx/orga/views/schedule.py index 6f15e9ec8..c3f0f7c15 100644 --- a/src/pretalx/orga/views/schedule.py +++ b/src/pretalx/orga/views/schedule.py @@ -23,14 +23,15 @@ from pretalx.agenda.management.commands.export_schedule_html import get_export_zip_path from pretalx.agenda.tasks import export_schedule_html from pretalx.common.language import get_current_language_information -from pretalx.common.mixins.views import ( +from pretalx.common.signals import register_data_exporters +from pretalx.common.text.path import safe_filename +from pretalx.common.views import CreateOrUpdateView +from pretalx.common.views.generic import OrderModelView +from pretalx.common.views.mixins import ( ActionFromUrl, EventPermissionRequired, PermissionRequired, ) -from pretalx.common.signals import register_data_exporters -from pretalx.common.text.path import safe_filename -from pretalx.common.views import CreateOrUpdateView, OrderModelView from pretalx.orga.forms.schedule import ( ScheduleExportForm, ScheduleReleaseForm, diff --git a/src/pretalx/orga/views/speaker.py b/src/pretalx/orga/views/speaker.py index 224bde0ef..fa2715277 100644 --- a/src/pretalx/orga/views/speaker.py +++ b/src/pretalx/orga/views/speaker.py @@ -11,7 +11,9 @@ from django_context_decorator import context from pretalx.common.exceptions import SendMailException -from pretalx.common.mixins.views import ( +from pretalx.common.signals import register_data_exporters +from pretalx.common.views import CreateOrUpdateView +from pretalx.common.views.mixins import ( ActionFromUrl, EventPermissionRequired, Filterable, @@ -19,8 +21,6 @@ PermissionRequired, Sortable, ) -from pretalx.common.signals import register_data_exporters -from pretalx.common.views import CreateOrUpdateView from pretalx.orga.forms.speaker import SpeakerExportForm from pretalx.person.forms import ( SpeakerFilterForm, diff --git a/src/pretalx/orga/views/submission.py b/src/pretalx/orga/views/submission.py index 80bd975ae..5c58f1284 100644 --- a/src/pretalx/orga/views/submission.py +++ b/src/pretalx/orga/views/submission.py @@ -31,16 +31,16 @@ from pretalx.agenda.permissions import is_submission_visible from pretalx.common.exceptions import SubmissionError -from pretalx.common.mixins.views import ( +from pretalx.common.models import ActivityLog +from pretalx.common.urls import build_absolute_uri +from pretalx.common.views import CreateOrUpdateView, context +from pretalx.common.views.mixins import ( ActionFromUrl, EventPermissionRequired, PaginationMixin, PermissionRequired, Sortable, ) -from pretalx.common.models import ActivityLog -from pretalx.common.urls import build_absolute_uri -from pretalx.common.views import CreateOrUpdateView, context from pretalx.mail.models import QueuedMail from pretalx.orga.forms.submission import ( AnonymiseForm, diff --git a/src/pretalx/person/forms.py b/src/pretalx/person/forms.py index 6fd9373ce..86a08286b 100644 --- a/src/pretalx/person/forms.py +++ b/src/pretalx/person/forms.py @@ -14,13 +14,13 @@ PasswordField, SizeFileField, ) -from pretalx.common.forms.widgets import MarkdownWidget -from pretalx.common.mixins.forms import ( +from pretalx.common.forms.mixins import ( I18nHelpText, PublicContent, ReadOnlyFlag, RequestRequire, ) +from pretalx.common.forms.widgets import MarkdownWidget from pretalx.common.text.phrases import phrases from pretalx.person.models import SpeakerInformation, SpeakerProfile, User from pretalx.schedule.forms import AvailabilitiesFormMixin diff --git a/src/pretalx/person/migrations/0001_initial.py b/src/pretalx/person/migrations/0001_initial.py index b92320268..cf1acef0c 100644 --- a/src/pretalx/person/migrations/0001_initial.py +++ b/src/pretalx/person/migrations/0001_initial.py @@ -3,7 +3,7 @@ from django.conf import settings from django.db import migrations, models import django.db.models.deletion -import pretalx.common.mixins +import pretalx.common.models.mixins import pretalx.person.models.user @@ -79,7 +79,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="SpeakerProfile", @@ -110,6 +110,6 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), ] diff --git a/src/pretalx/person/migrations/0008_data_populate_user_code.py b/src/pretalx/person/migrations/0008_data_populate_user_code.py index 6f6b488c2..0c86875f7 100644 --- a/src/pretalx/person/migrations/0008_data_populate_user_code.py +++ b/src/pretalx/person/migrations/0008_data_populate_user_code.py @@ -4,7 +4,7 @@ def populate_code(apps, schema_editor): - from pretalx.common.mixins.models import GenerateCode + from pretalx.common.models.mixins import GenerateCode User = apps.get_model("person", "User") for person in User.objects.all(): diff --git a/src/pretalx/person/migrations/0014_speakerinformation.py b/src/pretalx/person/migrations/0014_speakerinformation.py index dcb66b962..99295bb47 100644 --- a/src/pretalx/person/migrations/0014_speakerinformation.py +++ b/src/pretalx/person/migrations/0014_speakerinformation.py @@ -3,7 +3,7 @@ from django.db import migrations, models import django.db.models.deletion import i18nfield.fields -import pretalx.common.mixins.models +import pretalx.common.models.mixins class Migration(migrations.Migration): @@ -35,6 +35,6 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), ] diff --git a/src/pretalx/person/models/information.py b/src/pretalx/person/models/information.py index 473a92de5..0f041fc28 100644 --- a/src/pretalx/person/models/information.py +++ b/src/pretalx/person/models/information.py @@ -2,7 +2,7 @@ from django.utils.translation import gettext_lazy as _ from i18nfield.fields import I18nCharField, I18nTextField -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.text.path import path_with_hash from pretalx.common.text.phrases import phrases from pretalx.common.urls import EventUrls diff --git a/src/pretalx/person/models/profile.py b/src/pretalx/person/models/profile.py index c20f4b8a8..6f18b07fa 100644 --- a/src/pretalx/person/models/profile.py +++ b/src/pretalx/person/models/profile.py @@ -2,7 +2,7 @@ from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.text.phrases import phrases from pretalx.common.urls import EventUrls diff --git a/src/pretalx/person/models/user.py b/src/pretalx/person/models/user.py index 1cb528fe8..a46a341ca 100644 --- a/src/pretalx/person/models/user.py +++ b/src/pretalx/person/models/user.py @@ -21,8 +21,8 @@ from django_scopes import scopes_disabled from rest_framework.authtoken.models import Token -from pretalx.common.mixins.models import FileCleanupMixin, GenerateCode from pretalx.common.models import TIMEZONE_CHOICES +from pretalx.common.models.mixins import FileCleanupMixin, GenerateCode from pretalx.common.text.path import path_with_hash from pretalx.common.urls import build_absolute_uri diff --git a/src/pretalx/schedule/forms.py b/src/pretalx/schedule/forms.py index f3d5b1c42..13b7d6819 100644 --- a/src/pretalx/schedule/forms.py +++ b/src/pretalx/schedule/forms.py @@ -9,7 +9,7 @@ from i18nfield.forms import I18nModelForm from pretalx.api.serializers.room import AvailabilitySerializer -from pretalx.common.mixins.forms import ReadOnlyFlag +from pretalx.common.forms.mixins import ReadOnlyFlag from pretalx.schedule.models import Availability, Room, TalkSlot diff --git a/src/pretalx/schedule/migrations/0001_initial.py b/src/pretalx/schedule/migrations/0001_initial.py index ef33289b0..657cd9506 100644 --- a/src/pretalx/schedule/migrations/0001_initial.py +++ b/src/pretalx/schedule/migrations/0001_initial.py @@ -4,7 +4,7 @@ from django.db import migrations, models import django.db.models.deletion import i18nfield.fields -import pretalx.common.mixins +import pretalx.common.models.mixins class Migration(migrations.Migration): @@ -45,7 +45,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="Room", @@ -74,7 +74,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="Schedule", @@ -95,7 +95,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="TalkSlot", @@ -133,6 +133,6 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), ] diff --git a/src/pretalx/schedule/models/availability.py b/src/pretalx/schedule/models/availability.py index e5d27c2dc..7299fe9c0 100644 --- a/src/pretalx/schedule/models/availability.py +++ b/src/pretalx/schedule/models/availability.py @@ -3,7 +3,7 @@ from django.db import models from django.utils.functional import cached_property -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel zerotime = dt.time(0, 0) diff --git a/src/pretalx/schedule/models/room.py b/src/pretalx/schedule/models/room.py index 33cf6905f..414309a20 100644 --- a/src/pretalx/schedule/models/room.py +++ b/src/pretalx/schedule/models/room.py @@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _ from i18nfield.fields import I18nCharField -from pretalx.common.mixins.models import OrderedModel, PretalxModel +from pretalx.common.models.mixins import OrderedModel, PretalxModel from pretalx.common.models.settings import GlobalSettings from pretalx.common.urls import EventUrls diff --git a/src/pretalx/schedule/models/schedule.py b/src/pretalx/schedule/models/schedule.py index dc48530d6..007abf56e 100644 --- a/src/pretalx/schedule/models/schedule.py +++ b/src/pretalx/schedule/models/schedule.py @@ -13,7 +13,7 @@ from i18nfield.fields import I18nTextField from pretalx.agenda.tasks import export_schedule_html -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.text.phrases import phrases from pretalx.common.urls import EventUrls from pretalx.person.models import SpeakerProfile, User diff --git a/src/pretalx/schedule/models/slot.py b/src/pretalx/schedule/models/slot.py index fe98f3942..0cb922898 100644 --- a/src/pretalx/schedule/models/slot.py +++ b/src/pretalx/schedule/models/slot.py @@ -13,7 +13,7 @@ from django_scopes import ScopedManager from i18nfield.fields import I18nCharField -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.text.serialize import serialize_duration from pretalx.common.urls import get_base_url diff --git a/src/pretalx/submission/forms/feedback.py b/src/pretalx/submission/forms/feedback.py index 7e9eeabc4..9cb218819 100644 --- a/src/pretalx/submission/forms/feedback.py +++ b/src/pretalx/submission/forms/feedback.py @@ -1,8 +1,8 @@ from django import forms from django.utils.translation import gettext as _ +from pretalx.common.forms.mixins import ReadOnlyFlag from pretalx.common.forms.widgets import MarkdownWidget -from pretalx.common.mixins.forms import ReadOnlyFlag from pretalx.submission.models import Feedback diff --git a/src/pretalx/submission/forms/question.py b/src/pretalx/submission/forms/question.py index 61805d00d..44959f1e5 100644 --- a/src/pretalx/submission/forms/question.py +++ b/src/pretalx/submission/forms/question.py @@ -3,7 +3,7 @@ from django.utils.functional import cached_property from pretalx.cfp.forms.cfp import CfPFormMixin -from pretalx.common.mixins.forms import QuestionFieldsMixin +from pretalx.common.forms.mixins import QuestionFieldsMixin from pretalx.submission.models import Question, QuestionTarget, QuestionVariant diff --git a/src/pretalx/submission/forms/submission.py b/src/pretalx/submission/forms/submission.py index 8c1149785..f20e595e2 100644 --- a/src/pretalx/submission/forms/submission.py +++ b/src/pretalx/submission/forms/submission.py @@ -6,9 +6,9 @@ from pretalx.cfp.forms.cfp import CfPFormMixin from pretalx.common.forms.fields import ImageField +from pretalx.common.forms.mixins import PublicContent, RequestRequire from pretalx.common.forms.widgets import MarkdownWidget -from pretalx.common.mixins.forms import PublicContent, RequestRequire -from pretalx.common.mixins.views import Filterable +from pretalx.common.views.mixins import Filterable from pretalx.submission.forms.track_select_widget import TrackSelectWidget from pretalx.submission.models import Answer, Question, Submission, SubmissionStates diff --git a/src/pretalx/submission/forms/tag.py b/src/pretalx/submission/forms/tag.py index ca9f486c8..16871f7cd 100644 --- a/src/pretalx/submission/forms/tag.py +++ b/src/pretalx/submission/forms/tag.py @@ -2,7 +2,7 @@ from django.utils.translation import gettext_lazy as _ from i18nfield.forms import I18nModelForm -from pretalx.common.mixins.forms import I18nHelpText, ReadOnlyFlag +from pretalx.common.forms.mixins import I18nHelpText, ReadOnlyFlag from pretalx.submission.models import Tag diff --git a/src/pretalx/submission/migrations/0001_initial.py b/src/pretalx/submission/migrations/0001_initial.py index 511838075..a64b9e587 100644 --- a/src/pretalx/submission/migrations/0001_initial.py +++ b/src/pretalx/submission/migrations/0001_initial.py @@ -4,7 +4,7 @@ from django.db import migrations, models import django.db.models.deletion import i18nfield.fields -import pretalx.common.mixins +import pretalx.common.models.mixins class Migration(migrations.Migration): @@ -27,7 +27,7 @@ class Migration(migrations.Migration): ), ("answer", models.TextField()), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="AnswerOption", @@ -40,7 +40,7 @@ class Migration(migrations.Migration): ), ("answer", i18nfield.fields.I18nCharField(max_length=200)), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="CfP", @@ -60,7 +60,7 @@ class Migration(migrations.Migration): ("text", i18nfield.fields.I18nTextField(blank=True, null=True)), ("deadline", models.DateTimeField(blank=True, null=True)), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="Question", @@ -88,7 +88,7 @@ class Migration(migrations.Migration): options={ "ordering": ["position"], }, - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="Submission", @@ -125,7 +125,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="SubmissionType", @@ -148,7 +148,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.CreateModel( name="Track", @@ -170,7 +170,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.AddField( model_name="submission", diff --git a/src/pretalx/submission/migrations/0003_auto_20170830_1813.py b/src/pretalx/submission/migrations/0003_auto_20170830_1813.py index e90236121..da3b9efc0 100644 --- a/src/pretalx/submission/migrations/0003_auto_20170830_1813.py +++ b/src/pretalx/submission/migrations/0003_auto_20170830_1813.py @@ -3,7 +3,7 @@ from django.conf import settings from django.db import migrations, models import django.db.models.deletion -import pretalx.common.mixins +import pretalx.common.models.mixins class Migration(migrations.Migration): @@ -35,7 +35,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.AddField( model_name="submission", diff --git a/src/pretalx/submission/migrations/0014_resource.py b/src/pretalx/submission/migrations/0014_resource.py index add06eff5..32275e444 100644 --- a/src/pretalx/submission/migrations/0014_resource.py +++ b/src/pretalx/submission/migrations/0014_resource.py @@ -2,7 +2,7 @@ from django.db import migrations, models import django.db.models.deletion -import pretalx.common.mixins +import pretalx.common.models.mixins class Migration(migrations.Migration): @@ -34,6 +34,6 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), ] diff --git a/src/pretalx/submission/migrations/0041_auto_20191105_0042.py b/src/pretalx/submission/migrations/0041_auto_20191105_0042.py index 1ee4ab82b..52948234c 100644 --- a/src/pretalx/submission/migrations/0041_auto_20191105_0042.py +++ b/src/pretalx/submission/migrations/0041_auto_20191105_0042.py @@ -2,7 +2,7 @@ from django.db import migrations, models import django.db.models.deletion -import pretalx.common.mixins.models +import pretalx.common.models.mixins class Migration(migrations.Migration): @@ -71,8 +71,8 @@ class Migration(migrations.Migration): "unique_together": {("event", "code")}, }, bases=( - pretalx.common.mixins.models.LogMixin, - pretalx.common.mixins.models.GenerateCode, + pretalx.common.models.mixins.LogMixin, + pretalx.common.models.mixins.GenerateCode, models.Model, ), ), diff --git a/src/pretalx/submission/migrations/0052_auto_20201010_1307.py b/src/pretalx/submission/migrations/0052_auto_20201010_1307.py index fcbced8eb..eff953e7b 100644 --- a/src/pretalx/submission/migrations/0052_auto_20201010_1307.py +++ b/src/pretalx/submission/migrations/0052_auto_20201010_1307.py @@ -3,7 +3,7 @@ from django.db import migrations, models import django.db.models.deletion import i18nfield.fields -import pretalx.common.mixins.models +import pretalx.common.models.mixins class Migration(migrations.Migration): @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ), ), ], - bases=(pretalx.common.mixins.models.LogMixin, models.Model), + bases=(pretalx.common.models.mixins.LogMixin, models.Model), ), migrations.AddField( model_name="submission", diff --git a/src/pretalx/submission/models/access_code.py b/src/pretalx/submission/models/access_code.py index f84cfa0a4..cb6831abb 100644 --- a/src/pretalx/submission/models/access_code.py +++ b/src/pretalx/submission/models/access_code.py @@ -6,7 +6,7 @@ from django.utils.translation import get_language from django.utils.translation import gettext_lazy as _ -from pretalx.common.mixins.models import GenerateCode, PretalxModel +from pretalx.common.models.mixins import GenerateCode, PretalxModel from pretalx.common.urls import EventUrls diff --git a/src/pretalx/submission/models/cfp.py b/src/pretalx/submission/models/cfp.py index 7427ea1cb..d16de5a62 100644 --- a/src/pretalx/submission/models/cfp.py +++ b/src/pretalx/submission/models/cfp.py @@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _ from i18nfield.fields import I18nCharField, I18nTextField -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.text.phrases import phrases from pretalx.common.urls import EventUrls diff --git a/src/pretalx/submission/models/feedback.py b/src/pretalx/submission/models/feedback.py index f5186cedd..af2940850 100644 --- a/src/pretalx/submission/models/feedback.py +++ b/src/pretalx/submission/models/feedback.py @@ -3,7 +3,7 @@ from django.utils.translation import ngettext_lazy as _n from django_scopes import ScopedManager -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.text.phrases import phrases diff --git a/src/pretalx/submission/models/question.py b/src/pretalx/submission/models/question.py index a07528986..cbe976d36 100644 --- a/src/pretalx/submission/models/question.py +++ b/src/pretalx/submission/models/question.py @@ -5,8 +5,8 @@ from django_scopes import ScopedManager from i18nfield.fields import I18nCharField -from pretalx.common.mixins.models import OrderedModel, PretalxModel from pretalx.common.models.choices import Choices +from pretalx.common.models.mixins import OrderedModel, PretalxModel from pretalx.common.text.path import path_with_hash from pretalx.common.text.phrases import phrases from pretalx.common.urls import EventUrls diff --git a/src/pretalx/submission/models/resource.py b/src/pretalx/submission/models/resource.py index 007779c5f..18e868a79 100644 --- a/src/pretalx/submission/models/resource.py +++ b/src/pretalx/submission/models/resource.py @@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _ from django_scopes import ScopedManager -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.text.path import path_with_hash from pretalx.common.urls import get_base_url diff --git a/src/pretalx/submission/models/review.py b/src/pretalx/submission/models/review.py index 50b5456db..b21b4b205 100644 --- a/src/pretalx/submission/models/review.py +++ b/src/pretalx/submission/models/review.py @@ -5,7 +5,7 @@ from django_scopes import ScopedManager from i18nfield.fields import I18nCharField -from pretalx.common.mixins.models import OrderedModel, PretalxModel +from pretalx.common.models.mixins import OrderedModel, PretalxModel from pretalx.common.urls import EventUrls diff --git a/src/pretalx/submission/models/submission.py b/src/pretalx/submission/models/submission.py index e32387aaf..f1b7efbc1 100644 --- a/src/pretalx/submission/models/submission.py +++ b/src/pretalx/submission/models/submission.py @@ -19,8 +19,8 @@ from rest_framework import serializers from pretalx.common.exceptions import SubmissionError -from pretalx.common.mixins.models import GenerateCode, PretalxModel from pretalx.common.models.choices import Choices +from pretalx.common.models.mixins import GenerateCode, PretalxModel from pretalx.common.text.path import path_with_hash from pretalx.common.text.phrases import phrases from pretalx.common.text.serialize import serialize_duration diff --git a/src/pretalx/submission/models/tag.py b/src/pretalx/submission/models/tag.py index 0db2b5323..46e536f6b 100644 --- a/src/pretalx/submission/models/tag.py +++ b/src/pretalx/submission/models/tag.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from i18nfield.fields import I18nTextField -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.urls import EventUrls diff --git a/src/pretalx/submission/models/track.py b/src/pretalx/submission/models/track.py index f3eb4292b..4deb1f24d 100644 --- a/src/pretalx/submission/models/track.py +++ b/src/pretalx/submission/models/track.py @@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _ from i18nfield.fields import I18nCharField, I18nTextField -from pretalx.common.mixins.models import OrderedModel, PretalxModel +from pretalx.common.models.mixins import OrderedModel, PretalxModel from pretalx.common.urls import EventUrls diff --git a/src/pretalx/submission/models/type.py b/src/pretalx/submission/models/type.py index 9f4ccd59d..933dcc2ef 100644 --- a/src/pretalx/submission/models/type.py +++ b/src/pretalx/submission/models/type.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from i18nfield.fields import I18nCharField -from pretalx.common.mixins.models import PretalxModel +from pretalx.common.models.mixins import PretalxModel from pretalx.common.urls import EventUrls diff --git a/src/tests/common/test_common_utils.py b/src/tests/common/test_common_utils.py index 8d75c635d..ce2024fef 100644 --- a/src/tests/common/test_common_utils.py +++ b/src/tests/common/test_common_utils.py @@ -4,9 +4,9 @@ from django.utils import translation from i18nfield.strings import LazyI18nString -from pretalx.common.text.serialize import I18nStrJSONEncoder from pretalx.common.text.daterange import daterange from pretalx.common.text.path import safe_filename +from pretalx.common.text.serialize import I18nStrJSONEncoder @pytest.mark.parametrize( @@ -51,7 +51,9 @@ def test_daterange(locale, start, end, result): ), ) def test_path_with_hash(path, expected, monkeypatch): - monkeypatch.setattr("pretalx.common.text.path.get_random_string", lambda x: "aaaaaaa") + monkeypatch.setattr( + "pretalx.common.text.path.get_random_string", lambda x: "aaaaaaa" + ) from pretalx.common.text.path import path_with_hash assert path_with_hash(path) == expected diff --git a/src/tests/submission/test_submission_model.py b/src/tests/submission/test_submission_model.py index 41474ede5..a1d0d733c 100644 --- a/src/tests/submission/test_submission_model.py +++ b/src/tests/submission/test_submission_model.py @@ -289,7 +289,7 @@ def test_submission_change_slot_count(accepted_submission): @pytest.mark.django_db def test_submission_assign_code(submission, monkeypatch): - from pretalx.common.mixins import models as models_mixins + from pretalx.common.models import mixins as models_mixins from pretalx.submission.models import submission as pretalx_submission called = -1 From d926467b3879ed218b3fd181e7be96bae186314e Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 30 Apr 2024 14:08:01 +0200 Subject: [PATCH 038/195] Remove unused phrases --- src/pretalx/agenda/templates/agenda/feedback_form.html | 2 +- src/pretalx/cfp/phrases.py | 10 ---------- src/pretalx/cfp/views/user.py | 4 ++-- src/pretalx/orga/phrases.py | 7 ------- 4 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/pretalx/agenda/templates/agenda/feedback_form.html b/src/pretalx/agenda/templates/agenda/feedback_form.html index 09955c73c..baaa3f157 100644 --- a/src/pretalx/agenda/templates/agenda/feedback_form.html +++ b/src/pretalx/agenda/templates/agenda/feedback_form.html @@ -37,7 +37,7 @@

  • {% endif %} diff --git a/src/pretalx/cfp/phrases.py b/src/pretalx/cfp/phrases.py index cab1de0a8..a278e8ac8 100644 --- a/src/pretalx/cfp/phrases.py +++ b/src/pretalx/cfp/phrases.py @@ -14,11 +14,6 @@ class CfPPhrases(Phrases, app="cfp"): ) auth_reset_success = _("Awesome! You can now log in using your new password.") - locale_change_success = _( - "Your locale preferences have been saved. We like to think that we have excellent support " - "for English in pretalx, but if you encounter issues or errors, please contact us!" - ) - submission_withdrawn = _("Your proposal has been withdrawn.") submission_not_withdrawn = _( "Your proposal can’t be withdrawn at this time – please contact us if you need to withdraw your proposal!" @@ -34,17 +29,12 @@ class CfPPhrases(Phrases, app="cfp"): ) submission_uneditable = _("This proposal cannot be edited anymore.") - account_deleted = _("Your account has now been deleted.") - account_delete_confirm = _("Are you really sure? Please tick the box") - invite_invalid_email = _("Please provide a valid email address.") invite_sent = _("The invitation was sent!") invite_accepted = _( "You are now part of this proposal! Please fill in your profile below." ) - submissions_closed = _("This event currently does not accept new proposals, sorry!") - submission_success = _("Your session has been submitted successfully!") submission_email_fail = _( "We are experiencing difficulties when sending mails, but your session was submitted successfully!" ) diff --git a/src/pretalx/cfp/views/user.py b/src/pretalx/cfp/views/user.py index 1d9b6fcb9..5f3192766 100644 --- a/src/pretalx/cfp/views/user.py +++ b/src/pretalx/cfp/views/user.py @@ -442,9 +442,9 @@ def post(request, event): if request.POST.get("really"): request.user.deactivate() logout(request) - messages.success(request, phrases.cfp.account_deleted) + messages.success(request, _("Your account has now been deleted.")) return redirect(request.event.urls.base) - messages.error(request, phrases.cfp.account_delete_confirm) + messages.error(request, _("Are you really sure? Please tick the box")) return redirect(request.event.urls.user + "?really") diff --git a/src/pretalx/orga/phrases.py b/src/pretalx/orga/phrases.py index c394e92fb..6d34db407 100644 --- a/src/pretalx/orga/phrases.py +++ b/src/pretalx/orga/phrases.py @@ -4,13 +4,6 @@ class OrgaPhrases(Phrases, app="orga"): - schedule_example_version = [ - "v1", - "v2", - "v4.0", - "v0.1", - "♥", - ] example_review = [ _("I think this session is well-suited to this conference, because ..."), _("I think this session might fit the conference better, if ..."), From 36f02ea5a45f2dd3b12647db77213d115a632733 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Thu, 2 May 2024 18:09:39 +0200 Subject: [PATCH 039/195] Fix breaking tests --- src/pretalx/cfp/views/wizard.py | 3 ++- src/pretalx/common/forms/mixins.py | 16 +++++++++------- src/pretalx/common/views/__init__.py | 4 ++-- src/pretalx/orga/views/submission.py | 3 ++- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/pretalx/cfp/views/wizard.py b/src/pretalx/cfp/views/wizard.py index 7c900005c..34792cc7d 100644 --- a/src/pretalx/cfp/views/wizard.py +++ b/src/pretalx/cfp/views/wizard.py @@ -8,6 +8,7 @@ from django.urls import reverse from django.utils.crypto import get_random_string from django.utils.decorators import method_decorator +from django.utils.translation import gettext_lazy as _ from django.views import View from pretalx.cfp.views.event import EventPageMixin @@ -44,7 +45,7 @@ def dispatch(self, request, *args, **kwargs): if access_code and access_code.is_valid: request.access_code = access_code if not request.event.cfp.is_open and not request.access_code: - messages.error(request, phrases.cfp.submissions_closed) + messages.error(request, _("Proposals are closed")) return redirect( reverse("cfp:event.start", kwargs={"event": request.event.slug}) ) diff --git a/src/pretalx/common/forms/mixins.py b/src/pretalx/common/forms/mixins.py index 3f513935b..bc7f8de9f 100644 --- a/src/pretalx/common/forms/mixins.py +++ b/src/pretalx/common/forms/mixins.py @@ -122,15 +122,17 @@ def get_help_text(text, min_length, max_length, count_in="chars"): ) return (text + str(message)).strip() - @classmethod - def validate_field_length(cls, value, min_length, max_length, count_in): + @staticmethod + def validate_field_length(value, min_length, max_length, count_in): if count_in == "chars": # Line breaks should only be counted as one character length = len(value.replace("\r\n", "\n")) else: length = len(re.findall(r"\b\w+\b", value)) if (min_length and min_length > length) or (max_length and max_length < length): - error_message = cls.get_help_text("", min_length, max_length, count_in) + error_message = RequestRequire.get_help_text( + "", min_length, max_length, count_in + ) errors = { "chars": _("You wrote {count} characters."), "words": _("You wrote {count} words."), @@ -186,7 +188,7 @@ def get_field(self, *, question, initial, initial_object, readonly): if question.variant == QuestionVariant.STRING: field = forms.CharField( disabled=read_only, - help_text=self.get_help_text( + help_text=RequestRequire.get_help_text( help_text, question.min_length, question.max_length, @@ -202,7 +204,7 @@ def get_field(self, *, question, initial, initial_object, readonly): field.widget.attrs["placeholder"] = "" # XSS field.validators.append( partial( - self.validate_field_length, + RequestRequire.validate_field_length, min_length=question.min_length, max_length=question.max_length, count_in=self.event.cfp.settings["count_length_in"], @@ -226,7 +228,7 @@ def get_field(self, *, question, initial, initial_object, readonly): required=question.required, widget=forms.Textarea, disabled=read_only, - help_text=self.get_help_text( + help_text=RequestRequire.get_help_text( help_text, question.min_length, question.max_length, @@ -238,7 +240,7 @@ def get_field(self, *, question, initial, initial_object, readonly): ) field.validators.append( partial( - self.validate_field_length, + RequestRequire.validate_field_length, min_length=question.min_length, max_length=question.max_length, count_in=self.event.cfp.settings["count_length_in"], diff --git a/src/pretalx/common/views/__init__.py b/src/pretalx/common/views/__init__.py index 71d916980..35fe1a909 100644 --- a/src/pretalx/common/views/__init__.py +++ b/src/pretalx/common/views/__init__.py @@ -5,7 +5,7 @@ EventSocialMediaCard, GenericLoginView, GenericResetView, - OrderedModelView, + OrderModelView, ) from .helpers import get_static, is_form_bound @@ -14,7 +14,7 @@ "EventSocialMediaCard", "GenericLoginView", "GenericResetView", - "OrderedModelView", + "OrderModelView", "conditional_cache_page", "error_view", "get_static", diff --git a/src/pretalx/orga/views/submission.py b/src/pretalx/orga/views/submission.py index 5c58f1284..2bc9e05c6 100644 --- a/src/pretalx/orga/views/submission.py +++ b/src/pretalx/orga/views/submission.py @@ -28,12 +28,13 @@ UpdateView, View, ) +from django_context_decorator import context from pretalx.agenda.permissions import is_submission_visible from pretalx.common.exceptions import SubmissionError from pretalx.common.models import ActivityLog from pretalx.common.urls import build_absolute_uri -from pretalx.common.views import CreateOrUpdateView, context +from pretalx.common.views import CreateOrUpdateView from pretalx.common.views.mixins import ( ActionFromUrl, EventPermissionRequired, From caac8e4993ba68110714332c7f2a998c5010e1b5 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Thu, 2 May 2024 20:26:46 +0200 Subject: [PATCH 040/195] Use argon2 to hash passwords --- src/pretalx/settings.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pretalx/settings.py b/src/pretalx/settings.py index b035f64f5..59f314217 100644 --- a/src/pretalx/settings.py +++ b/src/pretalx/settings.py @@ -509,6 +509,13 @@ def merge_csp(*options, config=None): {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] +PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.Argon2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", + "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", + "django.contrib.auth.hashers.ScryptPasswordHasher", +] ## MIDDLEWARE SETTINGS From 2bcb53a92e1fe85f3f36bd1824cd4fa39cf25a98 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Thu, 2 May 2024 17:36:21 +0200 Subject: [PATCH 041/195] Reuse more translation strings --- .../agenda/templates/agenda/changelog.html | 2 +- .../templates/agenda/changelog_block.html | 32 +-- .../agenda/templates/agenda/feedback.html | 4 +- .../agenda/templates/agenda/header_row.html | 2 +- .../agenda/templates/agenda/schedule.html | 11 +- .../templates/agenda/schedule_nojs.html | 11 +- .../templates/agenda/session_block.html | 2 +- .../agenda/templates/agenda/speaker.html | 8 +- src/pretalx/agenda/templates/agenda/talk.html | 5 +- src/pretalx/cfp/flow.py | 28 ++ src/pretalx/cfp/forms/auth.py | 6 +- src/pretalx/cfp/forms/submissions.py | 28 +- src/pretalx/cfp/phrases.py | 22 ++ src/pretalx/cfp/templates/cfp/event/cfp.html | 6 +- .../templates/cfp/event/fragment_state.html | 8 +- .../cfp/templates/cfp/event/index.html | 68 ++--- .../cfp/templates/cfp/event/invitation.html | 2 +- .../cfp/templates/cfp/event/recover.html | 12 +- .../cfp/templates/cfp/event/reset.html | 6 +- .../templates/cfp/event/submission_base.html | 2 +- .../cfp/templates/cfp/event/user_profile.html | 6 +- .../cfp/event/user_submission_confirm.html | 14 +- .../cfp/event/user_submission_discard.html | 18 +- .../cfp/event/user_submission_edit.html | 107 +------- .../cfp/event/user_submission_invitation.html | 16 +- .../cfp/event/user_submission_withdraw.html | 13 +- .../includes/submission_resources_form.html | 93 +++++++ src/pretalx/cfp/views/user.py | 7 +- src/pretalx/common/log_display.py | 22 +- src/pretalx/common/templates/400.html | 4 +- src/pretalx/common/templates/403.html | 13 +- src/pretalx/common/templates/404.html | 13 +- src/pretalx/common/templates/500.html | 3 +- src/pretalx/common/templatetags/phrases.py | 20 ++ src/pretalx/common/views.py | 256 ------------------ src/pretalx/event/forms.py | 26 +- src/pretalx/event/models/event.py | 8 + src/pretalx/event/stages.py | 3 +- src/pretalx/mail/context.py | 2 +- src/pretalx/mail/models.py | 8 +- src/pretalx/orga/forms/cfp.py | 9 +- src/pretalx/orga/forms/event.py | 39 +-- src/pretalx/orga/forms/mails.py | 3 +- src/pretalx/orga/forms/review.py | 24 +- src/pretalx/orga/forms/schedule.py | 23 +- src/pretalx/orga/forms/speaker.py | 7 +- src/pretalx/orga/forms/submission.py | 12 +- src/pretalx/orga/phrases.py | 18 ++ .../orga/templates/orga/admin/update.html | 8 +- .../templates/orga/admin/user_delete.html | 2 +- .../templates/orga/admin/user_detail.html | 4 +- .../orga/templates/orga/admin/user_list.html | 2 +- .../orga/templates/orga/auth/recover.html | 12 +- .../orga/templates/orga/auth/reset.html | 6 +- src/pretalx/orga/templates/orga/base.html | 20 +- .../orga/cfp/access_code_delete.html | 2 +- .../templates/orga/cfp/access_code_form.html | 5 +- .../templates/orga/cfp/access_code_send.html | 10 +- .../templates/orga/cfp/question_delete.html | 2 +- .../templates/orga/cfp/question_remind.html | 5 +- .../templates/orga/cfp/question_view.html | 4 +- .../orga/cfp/submission_type_delete.html | 2 +- src/pretalx/orga/templates/orga/cfp/text.html | 2 +- .../orga/templates/orga/cfp/track_delete.html | 2 +- .../orga/templates/orga/event/delete.html | 2 +- .../templates/orga/includes/submit_row.html | 2 +- .../orga/mails/_placeholder_group.html | 8 +- .../mails/compose_reviewer_mail_form.html | 4 +- .../orga/templates/orga/mails/confirm.html | 2 +- .../templates/orga/mails/outbox_form.html | 4 +- .../templates/orga/mails/outbox_list.html | 10 +- .../orga/mails/send_draft_reminders.html | 6 +- .../orga/templates/orga/mails/sent_list.html | 2 +- .../orga/templates/orga/organiser/delete.html | 2 +- .../orga/templates/orga/organiser/detail.html | 10 +- .../templates/orga/review/assignment.html | 2 +- .../orga/templates/orga/review/bulk.html | 6 +- .../orga/templates/orga/review/dashboard.html | 6 +- .../review/regenerate_decision_mails.html | 2 +- .../orga/templates/orga/schedule/index.html | 3 +- .../orga/templates/orga/schedule/quick.html | 4 +- .../orga/templates/orga/schedule/release.html | 8 +- .../orga/templates/orga/settings/form.html | 2 +- .../orga/templates/orga/settings/mail.html | 4 +- .../orga/templates/orga/settings/review.html | 17 +- .../templates/orga/settings/team_delete.html | 4 +- .../templates/orga/settings/team_resend.html | 4 +- .../orga/settings/team_reset_password.html | 6 +- .../orga/templates/orga/settings/widget.html | 7 +- .../orga/templates/orga/speaker/form.html | 11 +- .../orga/speaker/information_delete.html | 2 +- .../orga/speaker/reset_password.html | 6 +- .../templates/orga/submission/anonymise.html | 2 +- .../orga/submission/apply_pending.html | 4 +- .../orga/templates/orga/submission/base.html | 2 +- .../templates/orga/submission/content.html | 2 +- .../orga/submission/feedback_list.html | 2 +- .../orga/submission/feedbacks_list.html | 2 +- .../orga/templates/orga/submission/list.html | 6 +- .../templates/orga/submission/review.html | 4 +- .../orga/submission/review_delete.html | 2 +- .../orga/submission/state_change.html | 2 +- .../orga/templates/orga/submission/stats.html | 4 +- .../templates/orga/submission/tag_delete.html | 2 +- src/pretalx/orga/views/dashboard.py | 8 +- src/pretalx/orga/views/event.py | 11 +- src/pretalx/orga/views/mails.py | 7 +- src/pretalx/orga/views/organiser.py | 11 +- src/pretalx/orga/views/schedule.py | 8 +- src/pretalx/orga/views/speaker.py | 6 +- src/pretalx/orga/views/submission.py | 7 + src/pretalx/person/forms.py | 28 +- src/pretalx/person/models/information.py | 7 +- src/pretalx/person/models/user.py | 4 +- src/pretalx/schedule/apps.py | 1 + src/pretalx/schedule/forms.py | 5 +- src/pretalx/schedule/models/schedule.py | 8 +- src/pretalx/schedule/models/slot.py | 15 +- src/pretalx/schedule/phrases.py | 32 +++ src/pretalx/submission/apps.py | 1 + src/pretalx/submission/forms/submission.py | 5 +- src/pretalx/submission/models/submission.py | 51 ++-- src/pretalx/submission/phrases.py | 15 + 123 files changed, 655 insertions(+), 895 deletions(-) create mode 100644 src/pretalx/cfp/templates/includes/submission_resources_form.html create mode 100644 src/pretalx/common/templatetags/phrases.py delete mode 100644 src/pretalx/common/views.py create mode 100644 src/pretalx/schedule/phrases.py create mode 100644 src/pretalx/submission/phrases.py diff --git a/src/pretalx/agenda/templates/agenda/changelog.html b/src/pretalx/agenda/templates/agenda/changelog.html index e8b94a0c4..4f9ccfcc5 100644 --- a/src/pretalx/agenda/templates/agenda/changelog.html +++ b/src/pretalx/agenda/templates/agenda/changelog.html @@ -8,7 +8,7 @@

    - {% translate "Version" %} {{ schedule.version }} + {{ phrases.schedule.version }} {{ schedule.version }} {{ schedule.published|date }}

    diff --git a/src/pretalx/agenda/templates/agenda/changelog_block.html b/src/pretalx/agenda/templates/agenda/changelog_block.html index 05c9bf3a1..925ac72d9 100644 --- a/src/pretalx/agenda/templates/agenda/changelog_block.html +++ b/src/pretalx/agenda/templates/agenda/changelog_block.html @@ -3,7 +3,7 @@ {% if schedule.comment %}

    {{ schedule.comment|rich_text }}

    {% elif schedule.changes.action == 'create' %} -

    {% translate "We released our first schedule!" %}

    +

    {{ phrases.schedule.first_schedule }}

    {% elif not schedule.changes.count %}

    {% endif %} @@ -14,10 +14,7 @@ @@ -25,10 +22,7 @@

    {% translate "We have a new session: " %} {% for talk in schedule.changes.new_talks %} - {{ quotation_open }}{{ talk.submission.title }}{{ quotation_close }} - {% if talk.submission.speakers.exists %} - {% translate "by" %} {{ talk.submission.display_speaker_names }} - {% endif %} + {{ talk.submission.display_title_with_speakers }} . {% endfor %} {% endif %}

    @@ -40,20 +34,14 @@
      {% for talk in schedule.changes.canceled_talks %}
    • - {{ quotation_open }}{{ talk.submission.title }}{{ quotation_close }} - {% if talk.submission.speakers.exists %} - {% translate "by" %} {{ talk.submission.display_speaker_names }} - {% endif %} + {{ talk.submission.display_title_with_speakers }}
    • {% endfor %}
    {% else %}

    {% translate "We sadly had to cancel a session: " %} {% for talk in schedule.changes.canceled_talks %} - {{ quotation_open }}{{ talk.submission.title }}{{ quotation_close }} - {% if talk.submission.speakers.exists %} - {% translate "by" %} {{ talk.submission.display_speaker_names }}. - {% endif %} + {{ talk.submission.display_title_with_speakers }} {% endfor %}

    {% endif %} {% endif %} @@ -64,10 +52,7 @@
    {% empty %} - {% translate "There has been no feedback for this session yet." %} + {{ phrases.schedule.no_feedback }} {% endfor %} {% endblock %} diff --git a/src/pretalx/agenda/templates/agenda/header_row.html b/src/pretalx/agenda/templates/agenda/header_row.html index 8f12637e9..1f3ca0509 100644 --- a/src/pretalx/agenda/templates/agenda/header_row.html +++ b/src/pretalx/agenda/templates/agenda/header_row.html @@ -79,7 +79,7 @@

    {% if with_extra %} - {% translate "Version" %} {{ schedule.version|default:"–" }} + {{ phrases.schedule.version }} {{ schedule.version|default:"–" }}

    @@ -30,7 +30,7 @@

    {% translate "Proposals by submission date" %}
    {% translate "Sessions by submission date" %}
    -
    +
    diff --git a/src/pretalx/orga/templates/orga/submission/tag_delete.html b/src/pretalx/orga/templates/orga/submission/tag_delete.html index f989359a5..3a027f74a 100644 --- a/src/pretalx/orga/templates/orga/submission/tag_delete.html +++ b/src/pretalx/orga/templates/orga/submission/tag_delete.html @@ -8,7 +8,7 @@

    {% csrf_token %}
    - {% translate "Back" %} + {{ phrases.base.back_button }}
    diff --git a/src/pretalx/orga/templates/orga/admin/user_detail.html b/src/pretalx/orga/templates/orga/admin/user_detail.html index 965f073d8..fc00b6e18 100644 --- a/src/pretalx/orga/templates/orga/admin/user_detail.html +++ b/src/pretalx/orga/templates/orga/admin/user_detail.html @@ -12,7 +12,7 @@

    {{ user.name }} {% csrf_token %} diff --git a/src/pretalx/orga/templates/orga/admin/user_list.html b/src/pretalx/orga/templates/orga/admin/user_list.html index 59aff9357..63b9295f1 100644 --- a/src/pretalx/orga/templates/orga/admin/user_list.html +++ b/src/pretalx/orga/templates/orga/admin/user_list.html @@ -75,7 +75,7 @@

    {% translate "Users" %}

    - + {% endfor %} diff --git a/src/pretalx/orga/templates/orga/cfp/access_code_delete.html b/src/pretalx/orga/templates/orga/cfp/access_code_delete.html index 4fd89f622..f13f455e0 100644 --- a/src/pretalx/orga/templates/orga/cfp/access_code_delete.html +++ b/src/pretalx/orga/templates/orga/cfp/access_code_delete.html @@ -12,7 +12,7 @@

    diff --git a/src/pretalx/orga/templates/orga/cfp/question_delete.html b/src/pretalx/orga/templates/orga/cfp/question_delete.html index 0645ed56f..41187e513 100644 --- a/src/pretalx/orga/templates/orga/cfp/question_delete.html +++ b/src/pretalx/orga/templates/orga/cfp/question_delete.html @@ -12,7 +12,7 @@

    diff --git a/src/pretalx/orga/templates/orga/cfp/question_view.html b/src/pretalx/orga/templates/orga/cfp/question_view.html index b5925722d..a05c266b4 100644 --- a/src/pretalx/orga/templates/orga/cfp/question_view.html +++ b/src/pretalx/orga/templates/orga/cfp/question_view.html @@ -62,9 +62,7 @@ {% include "orga/includes/order_object.html" with object=question %} - + diff --git a/src/pretalx/orga/templates/orga/cfp/submission_type_delete.html b/src/pretalx/orga/templates/orga/cfp/submission_type_delete.html index 4b4aee2ce..132aa12ef 100644 --- a/src/pretalx/orga/templates/orga/cfp/submission_type_delete.html +++ b/src/pretalx/orga/templates/orga/cfp/submission_type_delete.html @@ -12,7 +12,7 @@

    diff --git a/src/pretalx/orga/templates/orga/cfp/track_delete.html b/src/pretalx/orga/templates/orga/cfp/track_delete.html index 1d85f9ff4..6711119f4 100644 --- a/src/pretalx/orga/templates/orga/cfp/track_delete.html +++ b/src/pretalx/orga/templates/orga/cfp/track_delete.html @@ -12,7 +12,7 @@

    diff --git a/src/pretalx/orga/templates/orga/event/delete.html b/src/pretalx/orga/templates/orga/event/delete.html index 476d23d72..f9a0d87e1 100644 --- a/src/pretalx/orga/templates/orga/event/delete.html +++ b/src/pretalx/orga/templates/orga/event/delete.html @@ -13,7 +13,7 @@

    diff --git a/src/pretalx/orga/templates/orga/organiser/detail.html b/src/pretalx/orga/templates/orga/organiser/detail.html index 19aa683ec..d397b0a65 100644 --- a/src/pretalx/orga/templates/orga/organiser/detail.html +++ b/src/pretalx/orga/templates/orga/organiser/detail.html @@ -62,7 +62,7 @@ - + diff --git a/src/pretalx/orga/templates/orga/review/assignment.html b/src/pretalx/orga/templates/orga/review/assignment.html index bc6e70f8c..9c7d9c463 100644 --- a/src/pretalx/orga/templates/orga/review/assignment.html +++ b/src/pretalx/orga/templates/orga/review/assignment.html @@ -81,7 +81,7 @@

    {% translate "Assign reviewers" %}

    {% endif %} -
    + diff --git a/src/pretalx/orga/templates/orga/settings/team_detail.html b/src/pretalx/orga/templates/orga/settings/team_detail.html index 488613a56..e04d79852 100644 --- a/src/pretalx/orga/templates/orga/settings/team_detail.html +++ b/src/pretalx/orga/templates/orga/settings/team_detail.html @@ -31,7 +31,7 @@

    href="{% if request.event %}{{ request.event.orga_urls.team_settings }}{% else %}{{ request.organiser.orga_urls.teams }}{% endif %}{{ team.id }}/reset/{{ member.id }}" class="btn btn-sm btn-warning" > - {% translate "Reset password" %} + {{ phrases.base.password_reset_heading }} {% bootstrap_field form.name layout='event' %} {% bootstrap_field form.email layout='event' %} {% if form.biography %}{% bootstrap_field form.biography layout='event' %}{% endif %} {% if form.availabilities %} diff --git a/src/pretalx/orga/templates/orga/submission/review_fragment.html b/src/pretalx/orga/templates/orga/submission/review_fragment.html index 90366361c..3cefa027b 100644 --- a/src/pretalx/orga/templates/orga/submission/review_fragment.html +++ b/src/pretalx/orga/templates/orga/submission/review_fragment.html @@ -23,7 +23,7 @@ {% if show_reviewer_name %} {{ review.user }} {% elif not read_only %} -
    {% translate "Edit" %}
    +
    {{ phrases.base.edit }}
    {% endif %} diff --git a/src/pretalx/orga/templates/orga/submission/speakers.html b/src/pretalx/orga/templates/orga/submission/speakers.html index fd257064a..95631ec18 100644 --- a/src/pretalx/orga/templates/orga/submission/speakers.html +++ b/src/pretalx/orga/templates/orga/submission/speakers.html @@ -49,7 +49,7 @@

    {% translate "Other proposals by this speaker:" %}
    {% if can_edit_speakers %} - {% translate "Edit" %} + {{ phrases.base.edit }} diff --git a/src/pretalx/orga/views/admin.py b/src/pretalx/orga/views/admin.py index c4b0339da..8b9a7f8e4 100644 --- a/src/pretalx/orga/views/admin.py +++ b/src/pretalx/orga/views/admin.py @@ -68,7 +68,7 @@ def post(self, request, *args, **kwargs): def form_valid(self, form): form.save() - messages.success(self.request, _("Your changes have been saved.")) + messages.success(self.request, phrases.base.saved) return super().form_valid(form) def form_invalid(self, form): diff --git a/src/pretalx/orga/views/event.py b/src/pretalx/orga/views/event.py index 6d6d7e6d8..60a24ed55 100644 --- a/src/pretalx/orga/views/event.py +++ b/src/pretalx/orga/views/event.py @@ -543,11 +543,11 @@ def token(self): def post(self, request, *args, **kwargs): if self.login_form.is_bound and self.login_form.is_valid(): self.login_form.save() - messages.success(request, _("Your changes have been saved.")) + messages.success(request, phrases.base.saved) request.user.log_action("pretalx.user.password.update") elif self.profile_form.is_bound and self.profile_form.is_valid(): self.profile_form.save() - messages.success(request, _("Your changes have been saved.")) + messages.success(request, phrases.base.saved) request.user.log_action("pretalx.user.profile.update") elif request.POST.get("form") == "token": request.user.regenerate_token() diff --git a/src/pretalx/orga/views/plugins.py b/src/pretalx/orga/views/plugins.py index 2ccb242a5..a02eb07a6 100644 --- a/src/pretalx/orga/views/plugins.py +++ b/src/pretalx/orga/views/plugins.py @@ -2,11 +2,11 @@ from django.db import transaction from django.shortcuts import redirect from django.utils.functional import cached_property -from django.utils.translation import gettext_lazy as _ from django.views.generic import TemplateView from django_context_decorator import context from pretalx.common.plugins import get_all_plugins_grouped +from pretalx.common.text.phrases import phrases from pretalx.common.views.mixins import EventPermissionRequired @@ -49,5 +49,5 @@ def post(self, request, *args, **kwargs): orga=True, ) self.request.event.save() - messages.success(self.request, _("Your changes have been saved.")) + messages.success(self.request, phrases.base.saved) return redirect(self.request.event.orga_urls.plugins) diff --git a/src/pretalx/orga/views/speaker.py b/src/pretalx/orga/views/speaker.py index 2f87a9366..e1ff12e64 100644 --- a/src/pretalx/orga/views/speaker.py +++ b/src/pretalx/orga/views/speaker.py @@ -248,12 +248,6 @@ def post(self, request, *args, **kwargs): messages.success(self.request, phrases.orga.password_reset_success) except SendMailException: # pragma: no cover messages.error(self.request, phrases.orga.password_reset_fail) - messages.error( - self.request, - _( - "The password reset email could not be sent, so the password was not reset." - ), - ) return redirect(user.event_profile(self.request.event).orga_urls.base) diff --git a/src/pretalx/person/forms.py b/src/pretalx/person/forms.py index 97b5682e0..e1cf30166 100644 --- a/src/pretalx/person/forms.py +++ b/src/pretalx/person/forms.py @@ -47,7 +47,7 @@ class UserForm(CfPFormMixin, forms.Form): widget=forms.TextInput(attrs={"autocomplete": "name"}), ) register_email = forms.EmailField( - label=_("Email address"), + label=phrases.base.enter_email, required=False, widget=forms.EmailInput(attrs={"autocomplete": "email"}), ) @@ -70,7 +70,9 @@ class UserForm(CfPFormMixin, forms.Form): def __init__(self, *args, **kwargs): kwargs.pop("event", None) super().__init__(*args, **kwargs) - self.fields["register_email"].widget.attrs = {"placeholder": _("Email address")} + self.fields["register_email"].widget.attrs = { + "placeholder": phrases.base.enter_email + } def _clean_login(self, data): try: From 7f0867d59a72fab39d9304c3e69bb5ee5d8e443f Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 3 May 2024 15:43:43 +0200 Subject: [PATCH 043/195] Use common template for delete/action confirm views --- src/pretalx/common/log_display.py | 2 +- .../templates/common/action_confirm.html | 7 ++++ .../common/includes/action_confirm.html | 33 +++++++++++++++++++ .../templates/orga/admin/user_delete.html | 19 ----------- .../orga/cfp/access_code_delete.html | 20 ----------- .../templates/orga/cfp/question_delete.html | 19 ----------- .../orga/cfp/submission_type_delete.html | 19 ----------- .../orga/templates/orga/cfp/track_delete.html | 19 ----------- .../orga/templates/orga/event/delete.html | 20 ----------- .../orga/templates/orga/mails/confirm.html | 19 ----------- .../orga/templates/orga/organiser/delete.html | 22 ------------- .../review/regenerate_decision_mails.html | 23 ------------- .../templates/orga/settings/team_delete.html | 23 ------------- .../templates/orga/settings/team_resend.html | 23 ------------- .../orga/settings/team_reset_password.html | 23 ------------- .../orga/speaker/information_delete.html | 19 ----------- .../orga/speaker/reset_password.html | 23 ------------- .../orga/submission/review_delete.html | 16 +-------- .../templates/orga/submission/tag_delete.html | 19 ----------- src/pretalx/orga/views/admin.py | 9 ++--- src/pretalx/orga/views/cfp.py | 13 +++----- src/pretalx/orga/views/event.py | 26 ++++++++------- src/pretalx/orga/views/mails.py | 16 +++++---- src/pretalx/orga/views/organiser.py | 24 ++++++++------ src/pretalx/orga/views/review.py | 20 ++++++++--- src/pretalx/orga/views/speaker.py | 26 ++++++++++++--- src/pretalx/orga/views/submission.py | 19 +++++------ 27 files changed, 134 insertions(+), 387 deletions(-) create mode 100644 src/pretalx/common/templates/common/action_confirm.html create mode 100644 src/pretalx/common/templates/common/includes/action_confirm.html delete mode 100644 src/pretalx/orga/templates/orga/admin/user_delete.html delete mode 100644 src/pretalx/orga/templates/orga/cfp/access_code_delete.html delete mode 100644 src/pretalx/orga/templates/orga/cfp/question_delete.html delete mode 100644 src/pretalx/orga/templates/orga/cfp/submission_type_delete.html delete mode 100644 src/pretalx/orga/templates/orga/cfp/track_delete.html delete mode 100644 src/pretalx/orga/templates/orga/event/delete.html delete mode 100644 src/pretalx/orga/templates/orga/mails/confirm.html delete mode 100644 src/pretalx/orga/templates/orga/organiser/delete.html delete mode 100644 src/pretalx/orga/templates/orga/review/regenerate_decision_mails.html delete mode 100644 src/pretalx/orga/templates/orga/settings/team_delete.html delete mode 100644 src/pretalx/orga/templates/orga/settings/team_resend.html delete mode 100644 src/pretalx/orga/templates/orga/settings/team_reset_password.html delete mode 100644 src/pretalx/orga/templates/orga/speaker/information_delete.html delete mode 100644 src/pretalx/orga/templates/orga/speaker/reset_password.html delete mode 100644 src/pretalx/orga/templates/orga/submission/tag_delete.html diff --git a/src/pretalx/common/log_display.py b/src/pretalx/common/log_display.py index 0c80d4918..7044f1bed 100644 --- a/src/pretalx/common/log_display.py +++ b/src/pretalx/common/log_display.py @@ -110,7 +110,7 @@ "pretalx.speaker.arrived": _("A speaker has been marked as arrived."), "pretalx.speaker.unarrived": _("A speaker has been marked as not arrived."), "pretalx.user.token.reset": _("The API token was reset."), - "pretalx.user.password.reset": phrases.base.password_reset_sucess, + "pretalx.user.password.reset": phrases.base.password_reset_success, "pretalx.user.password.update": _("The password was modified."), "pretalx.user.profile.update": _("The profile was modified."), } diff --git a/src/pretalx/common/templates/common/action_confirm.html b/src/pretalx/common/templates/common/action_confirm.html new file mode 100644 index 000000000..b71d2d5fd --- /dev/null +++ b/src/pretalx/common/templates/common/action_confirm.html @@ -0,0 +1,7 @@ +{% extends "orga/base.html" %} + +{% block title %}{{ action_title }}{% endblock %} + +{% block content %} + {% include "common/includes/action_confirm.html" %} +{% endblock %} diff --git a/src/pretalx/common/templates/common/includes/action_confirm.html b/src/pretalx/common/templates/common/includes/action_confirm.html new file mode 100644 index 000000000..416762b94 --- /dev/null +++ b/src/pretalx/common/templates/common/includes/action_confirm.html @@ -0,0 +1,33 @@ +{% load i18n %} +

    {{ action_title }}

    +
    +

    {{ action_object_name }}

    +

    {{ action_text }}

    +
    + {% csrf_token %} +
    + + {% if action_back_icon %}{% endif %} + {{ action_back_label }} + +
    + {% for action in additional_actions %} + {% if action.href %} + + + {{ action.label }} + + {% else %} + + {% endif %} + {% endfor %} + +
    +
    +
    diff --git a/src/pretalx/orga/templates/orga/admin/user_delete.html b/src/pretalx/orga/templates/orga/admin/user_delete.html deleted file mode 100644 index acf897b81..000000000 --- a/src/pretalx/orga/templates/orga/admin/user_delete.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "orga/base.html" %} -{% load i18n %} -{% block content %} -

    - {% translate "Do you really want to delete this user?" %} -

    - {{ quotation_close }}{{ object.name }}{{ quotation_open }} – {{ object.text }}

    -

    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/cfp/access_code_delete.html b/src/pretalx/orga/templates/orga/cfp/access_code_delete.html deleted file mode 100644 index f13f455e0..000000000 --- a/src/pretalx/orga/templates/orga/cfp/access_code_delete.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "orga/cfp/base.html" %} -{% load i18n %} -{% block content %} -

    - {% translate "Do you really want to delete this access code?" %} – {{ quotation_open }}{{ object.code }}{{ quotation_close }} -

    -
    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} - diff --git a/src/pretalx/orga/templates/orga/cfp/question_delete.html b/src/pretalx/orga/templates/orga/cfp/question_delete.html deleted file mode 100644 index 41187e513..000000000 --- a/src/pretalx/orga/templates/orga/cfp/question_delete.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "orga/cfp/base.html" %} -{% load i18n %} -{% block content %} -

    - {% translate "Do you really want to delete this question?" %} – {{ quotation_open }}{{ object.question }}{{ quotation_close }} -

    -
    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/cfp/submission_type_delete.html b/src/pretalx/orga/templates/orga/cfp/submission_type_delete.html deleted file mode 100644 index 132aa12ef..000000000 --- a/src/pretalx/orga/templates/orga/cfp/submission_type_delete.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "orga/cfp/base.html" %} -{% load i18n %} -{% block content %} -

    - {% translate "Do you really want to delete this session type?" %} – {{ quotation_open }}{{ object.name }}{{ quotation_close }} -

    -
    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/cfp/track_delete.html b/src/pretalx/orga/templates/orga/cfp/track_delete.html deleted file mode 100644 index 6711119f4..000000000 --- a/src/pretalx/orga/templates/orga/cfp/track_delete.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "orga/cfp/base.html" %} -{% load i18n %} -{% block content %} -

    - {% translate "Do you really want to delete this track?" %} – {{ quotation_open }}{{ object.name }}{{ quotation_close }} -

    -
    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/event/delete.html b/src/pretalx/orga/templates/orga/event/delete.html deleted file mode 100644 index f9a0d87e1..000000000 --- a/src/pretalx/orga/templates/orga/event/delete.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "orga/cfp/base.html" %} -{% load i18n %} -{% block content %} -

    - {% translate "Do you really want to delete this event?" %} -

    - {{ quotation_open }}{{ request.event.name }}{{ quotation_close }} – {% translate "ALL related data, such as proposals, and speaker profiles, and uploads, will also be deleted and cannot be restored." %}

    -

    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/mails/confirm.html b/src/pretalx/orga/templates/orga/mails/confirm.html deleted file mode 100644 index 200b352e6..000000000 --- a/src/pretalx/orga/templates/orga/mails/confirm.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "orga/mails/base.html" %} -{% load i18n %} -{% block mail_content %} -

    - {% translate "Please confirm:" %} -

    - {{ question }} -
    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/organiser/delete.html b/src/pretalx/orga/templates/orga/organiser/delete.html deleted file mode 100644 index 4887f4550..000000000 --- a/src/pretalx/orga/templates/orga/organiser/delete.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "orga/cfp/base.html" %} -{% load i18n %} -{% block content %} -

    - {% translate "Do you really want to delete this organiser?" %} -

    - - {{ quotation_open }}{{ request.organiser.name }}{{ quotation_close }} - – {% translate "ALL related data for ALL events, such as proposals, and speaker profiles, and uploads, will also be deleted and cannot be restored." %}

    -

    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/review/regenerate_decision_mails.html b/src/pretalx/orga/templates/orga/review/regenerate_decision_mails.html deleted file mode 100644 index 71391c58f..000000000 --- a/src/pretalx/orga/templates/orga/review/regenerate_decision_mails.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "orga/base.html" %} -{% load i18n %} -{% block content %} -

    - {% translate "Regenerate notification emails" %} -

    - {% blocktranslate trimmed with count=count %} - Do you really want to regenerate {{ count }} acceptance and rejection emails? - They will be placed in the outbox and not sent out directly. - {% endblocktranslate %} - {{ question }} -
    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/settings/team_delete.html b/src/pretalx/orga/templates/orga/settings/team_delete.html deleted file mode 100644 index 6d181e2f6..000000000 --- a/src/pretalx/orga/templates/orga/settings/team_delete.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "orga/settings/base.html" %} -{% load i18n %} - -{% block settings_content %} -
    -

    - {% translate "Team" %} {{ quotation_open }}{{ team.name }}{{ quotation_close }} -

    - - {% translate "Do you really want to go through with this deletion?" %} {{ object.email }} -
    - - -
    {% csrf_token %} - - {{ phrases.base.back_button }} - - -
    -
    -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/settings/team_resend.html b/src/pretalx/orga/templates/orga/settings/team_resend.html deleted file mode 100644 index dc92e5d99..000000000 --- a/src/pretalx/orga/templates/orga/settings/team_resend.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "orga/settings/base.html" %} -{% load i18n %} - -{% block settings_content %} -
    -

    - {% translate "Team" %} {{ quotation_open }}{{ team.name }}{{ quotation_close }} -

    - - {% translate "Do you want to resend the email to:" %} {{ object.email }} -
    - - -
    {% csrf_token %} - - {{ phrases.base.back_button }} - - -
    -
    -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/settings/team_reset_password.html b/src/pretalx/orga/templates/orga/settings/team_reset_password.html deleted file mode 100644 index 4253a5b39..000000000 --- a/src/pretalx/orga/templates/orga/settings/team_reset_password.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "orga/settings/base.html" %} -{% load i18n %} - -{% block settings_content %} -
    -

    - {{ phrases.base.password_reset_heading }}: {{ member.get_display_name }} ({{ member.name }}) -

    - - {% translate "Do you really want to reset this user’s password? They won’t be able to log in until they set a new password. The email will go to: " %} {{ member.email }}. -
    - - -
    {% csrf_token %} - - {{ phrases.base.back_button }} - - -
    -
    -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/speaker/information_delete.html b/src/pretalx/orga/templates/orga/speaker/information_delete.html deleted file mode 100644 index 62a269dd8..000000000 --- a/src/pretalx/orga/templates/orga/speaker/information_delete.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "orga/cfp/base.html" %} -{% load i18n %} -{% block content %} -

    - {% translate "Do you really want to delete this information?" %} -

    - {{ quotation_close }}{{ object.title }}{{ quotation_open }} – {{ object.text }}

    -

    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/speaker/reset_password.html b/src/pretalx/orga/templates/orga/speaker/reset_password.html deleted file mode 100644 index f1c945de4..000000000 --- a/src/pretalx/orga/templates/orga/speaker/reset_password.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "orga/settings/base.html" %} -{% load i18n %} - -{% block content %} -
    -

    - {{ phrases.base.password_reset_heading }}: {{ speaker.get_display_name }} -

    - - {% translate "Do you really want to reset this user’s password? They won’t be able to log in until they set a new password. The email will go to: " %} {{ speaker.email }}. -
    - - -
    {% csrf_token %} - - {{ phrases.base.back_button }} - - -
    -
    -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/templates/orga/submission/review_delete.html b/src/pretalx/orga/templates/orga/submission/review_delete.html index f470a98e8..3e0c457e6 100644 --- a/src/pretalx/orga/templates/orga/submission/review_delete.html +++ b/src/pretalx/orga/templates/orga/submission/review_delete.html @@ -1,19 +1,5 @@ {% extends "orga/submission/base.html" %} {% load i18n %} {% block submission_content %} -

    - {% translate "Do you really want to delete your review?" %} -

    -
    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    + {% include "common/includes/action_confirm.html" %} {% endblock %} diff --git a/src/pretalx/orga/templates/orga/submission/tag_delete.html b/src/pretalx/orga/templates/orga/submission/tag_delete.html deleted file mode 100644 index 3a027f74a..000000000 --- a/src/pretalx/orga/templates/orga/submission/tag_delete.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "orga/cfp/base.html" %} -{% load i18n %} -{% block content %} -

    - {% translate "Do you really want to delete this tag?" %} – {{ quotation_open }}{{ object.tag }}{{ quotation_close }} -

    -
    - {% csrf_token %} -
    - - {{ phrases.base.back_button }} - - -
    -
    -{% endblock %} diff --git a/src/pretalx/orga/views/admin.py b/src/pretalx/orga/views/admin.py index 8b9a7f8e4..2292940d2 100644 --- a/src/pretalx/orga/views/admin.py +++ b/src/pretalx/orga/views/admin.py @@ -7,12 +7,7 @@ from django.shortcuts import redirect from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from django.views.generic import ( - DetailView, - FormView, - ListView, - TemplateView, -) +from django.views.generic import DetailView, FormView, ListView, TemplateView from django_context_decorator import context from django_scopes import scopes_disabled @@ -20,7 +15,7 @@ from pretalx.common.models.settings import GlobalSettings from pretalx.common.text.phrases import phrases from pretalx.common.update_check import check_result_table, update_check -from pretalx.common.views.mixins import PermissionRequired, ActionConfirmMixin +from pretalx.common.views.mixins import ActionConfirmMixin, PermissionRequired from pretalx.orga.forms.admin import UpdateSettingsForm from pretalx.person.models import User diff --git a/src/pretalx/orga/views/cfp.py b/src/pretalx/orga/views/cfp.py index b61567e75..137bc957a 100644 --- a/src/pretalx/orga/views/cfp.py +++ b/src/pretalx/orga/views/cfp.py @@ -21,6 +21,7 @@ from pretalx.common.views import CreateOrUpdateView from pretalx.common.views.generic import OrderModelView from pretalx.common.views.mixins import ( + ActionConfirmMixin, ActionFromUrl, EventPermissionRequired, PaginationMixin, @@ -300,9 +301,8 @@ def form_valid(self, form): return result -class CfPQuestionDelete(PermissionRequired, DetailView): +class CfPQuestionDelete(PermissionRequired, ActionConfirmMixin, DetailView): permission_required = "orga.remove_question" - template_name = "orga/cfp/question_delete.html" def get_object(self, queryset=None) -> Question: return get_object_or_404( @@ -487,9 +487,8 @@ def dispatch(self, request, *args, **kwargs): return redirect(self.request.event.cfp.urls.types) -class SubmissionTypeDelete(PermissionRequired, DetailView): +class SubmissionTypeDelete(PermissionRequired, ActionConfirmMixin, DetailView): permission_required = "orga.remove_submission_type" - template_name = "orga/cfp/submission_type_delete.html" def get_object(self, queryset=None): return get_object_or_404( @@ -579,9 +578,8 @@ def form_valid(self, form): return result -class TrackDelete(PermissionRequired, DetailView): +class TrackDelete(PermissionRequired, ActionConfirmMixin, DetailView): permission_required = "orga.remove_track" - template_name = "orga/cfp/track_delete.html" def get_object(self, queryset=None): return get_object_or_404(self.request.event.tracks, pk=self.kwargs.get("pk")) @@ -694,9 +692,8 @@ def form_valid(self, form): return result -class AccessCodeDelete(PermissionRequired, DetailView): +class AccessCodeDelete(PermissionRequired, ActionConfirmMixin, DetailView): permission_required = "orga.remove_access_code" - template_name = "orga/cfp/access_code_delete.html" def get_object(self, queryset=None): return get_object_or_404( diff --git a/src/pretalx/orga/views/event.py b/src/pretalx/orga/views/event.py index 60a24ed55..87537d835 100644 --- a/src/pretalx/orga/views/event.py +++ b/src/pretalx/orga/views/event.py @@ -16,14 +16,7 @@ from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy -from django.views.generic import ( - DeleteView, - FormView, - ListView, - TemplateView, - UpdateView, - View, -) +from django.views.generic import FormView, ListView, TemplateView, UpdateView, View from django_context_decorator import context from django_scopes import scope, scopes_disabled from formtools.wizard.views import SessionWizardView @@ -36,6 +29,7 @@ from pretalx.common.text.phrases import phrases from pretalx.common.views import OrderModelView, is_form_bound from pretalx.common.views.mixins import ( + ActionConfirmMixin, ActionFromUrl, EventPermissionRequired, PermissionRequired, @@ -347,7 +341,7 @@ def save_scores(self): return True -class ScoreCategoryDelete(PermissionRequired, View): +class ScoreCategoryDelete(PermissionRequired, ActionConfirmMixin, TemplateView): permission_required = "orga.change_settings" def get_object(self): @@ -377,7 +371,7 @@ def get_success_url(self): return self.request.event.orga_urls.review_settings -class PhaseDelete(PermissionRequired, View): +class PhaseDelete(PermissionRequired, ActionConfirmMixin, TemplateView): permission_required = "orga.change_settings" def get_object(self): @@ -393,6 +387,7 @@ def action_back_url(self): return self.request.event.orga_urls.review_settings def post(self, request, *args, **kwargs): + super().dispatch(request, *args, **kwargs) phase = self.get_object() phase.delete() return redirect(self.request.event.orga_urls.review_settings) @@ -714,10 +709,17 @@ def done(self, form_list, *args, **kwargs): return redirect(event.orga_urls.base + "?congratulations") -class EventDelete(PermissionRequired, DeleteView): - template_name = "orga/event/delete.html" +class EventDelete(PermissionRequired, ActionConfirmMixin, TemplateView): permission_required = "person.is_administrator" model = Event + action_text = ( + _( + "ALL related data, such as proposals, and speaker profiles, and " + "uploads, will also be deleted and cannot be restored." + ) + + " " + + phrases.base.delete_warning + ) def get_object(self): return self.request.event diff --git a/src/pretalx/orga/views/mails.py b/src/pretalx/orga/views/mails.py index 3c5161030..92d71c9a2 100644 --- a/src/pretalx/orga/views/mails.py +++ b/src/pretalx/orga/views/mails.py @@ -14,6 +14,7 @@ from pretalx.common.text.phrases import phrases from pretalx.common.views import CreateOrUpdateView from pretalx.common.views.mixins import ( + ActionConfirmMixin, ActionFromUrl, EventPermissionRequired, Filterable, @@ -85,9 +86,12 @@ def get_queryset(self): return self.sort_queryset(qs) -class OutboxSend(EventPermissionRequired, TemplateView): +class OutboxSend(EventPermissionRequired, ActionConfirmMixin, TemplateView): permission_required = "orga.send_mails" - template_name = "orga/mails/confirm.html" + action_object_name = "" + action_confirm_label = phrases.base.send + action_confirm_color = "success" + action_confirm_icon = "envelope" @context def question(self): @@ -145,9 +149,9 @@ def post(self, request, *args, **kwargs): return redirect(self.request.event.orga_urls.outbox) -class MailDelete(PermissionRequired, TemplateView): +class MailDelete(PermissionRequired, ActionConfirmMixin, TemplateView): permission_required = "orga.purge_mails" - template_name = "orga/mails/confirm.html" + action_object_name = "" def get_permission_object(self): return self.request.event @@ -210,9 +214,9 @@ def post(self, request, *args, **kwargs): return redirect(request.event.orga_urls.outbox) -class OutboxPurge(PermissionRequired, TemplateView): +class OutboxPurge(PermissionRequired, ActionConfirmMixin, TemplateView): permission_required = "orga.purge_mails" - template_name = "orga/mails/confirm.html" + action_object_name = "" def get_permission_object(self): return self.request.event diff --git a/src/pretalx/orga/views/organiser.py b/src/pretalx/orga/views/organiser.py index 5921157c3..e0065064d 100644 --- a/src/pretalx/orga/views/organiser.py +++ b/src/pretalx/orga/views/organiser.py @@ -7,14 +7,14 @@ from django.shortcuts import get_object_or_404, redirect from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ -from django.views.generic import DeleteView, DetailView, TemplateView +from django.views.generic import DetailView, TemplateView from django_context_decorator import context from pretalx.common.exceptions import SendMailException from pretalx.common.text.phrases import phrases from pretalx.common.views import CreateOrUpdateView from pretalx.common.views import is_form_bound -from pretalx.common.views.mixins import PermissionRequired +from pretalx.common.views.mixins import ActionConfirmMixin, PermissionRequired from pretalx.event.forms import OrganiserForm, TeamForm, TeamInviteForm from pretalx.event.models import Organiser, Team, TeamInvite from pretalx.orga.forms.sso_client_form import SSOClientForm @@ -107,9 +107,8 @@ def get_success_url(self): return self.request.GET.get("next", self.request.path) -class TeamDelete(PermissionRequired, TeamMixin, DetailView): +class TeamDelete(PermissionRequired, TeamMixin, ActionConfirmMixin, DetailView): permission_required = "orga.change_teams" - template_name = "orga/settings/team_delete.html" def get_permission_object(self): return self._get_team() @@ -184,9 +183,8 @@ def post(self, request, *args, **kwargs): return redirect(self.request.organiser.orga_urls.base) -class TeamResend(InviteMixin, PermissionRequired, DetailView): +class TeamResend(InviteMixin, PermissionRequired, ActionConfirmMixin, DetailView): model = TeamInvite - template_name = "orga/settings/team_resend.html" permission_required = "orga.change_teams" action_title = _("Resend invitation") action_text = _("Are you sure you want to resend the invitation to this user?") @@ -207,9 +205,8 @@ def post(self, request, *args, **kwargs): return redirect(self.request.organiser.orga_urls.base) -class TeamResetPassword(PermissionRequired, TemplateView): +class TeamResetPassword(PermissionRequired, ActionConfirmMixin, TemplateView): model = Team - template_name = "orga/settings/team_reset_password.html" permission_required = "orga.change_teams" action_confirm_icon = "key" action_confirm_label = phrases.base.password_reset_heading @@ -336,10 +333,17 @@ def handle_sso_client(self, request, *args, **kwargs): return self.save_sso_client(request, *args, **kwargs) -class OrganiserDelete(PermissionRequired, DeleteView): - template_name = "orga/organiser/delete.html" +class OrganiserDelete(PermissionRequired, ActionConfirmMixin, DetailView): permission_required = "person.is_administrator" model = Organiser + action_text = ( + _( + "ALL related data for ALL events, such as proposals, and speaker profiles, and uploads, " + "will also be deleted and cannot be restored." + ) + + " " + + phrases.base.delete_warning + ) def get_object(self, queryset=None): return getattr(self.request, "organiser", None) diff --git a/src/pretalx/orga/views/review.py b/src/pretalx/orga/views/review.py index ade15ce82..d2401e692 100644 --- a/src/pretalx/orga/views/review.py +++ b/src/pretalx/orga/views/review.py @@ -12,7 +12,11 @@ from django_context_decorator import context from pretalx.common.views import CreateOrUpdateView -from pretalx.common.views.mixins import EventPermissionRequired, PermissionRequired +from pretalx.common.views.mixins import ( + ActionConfirmMixin, + EventPermissionRequired, + PermissionRequired, +) from pretalx.orga.forms.review import ( DirectionForm, ProposalForReviewerForm, @@ -652,7 +656,9 @@ def get_success_url(self) -> str: return self.request.event.orga_urls.reviews -class ReviewSubmissionDelete(EventPermissionRequired, ReviewViewMixin, TemplateView): +class ReviewSubmissionDelete( + EventPermissionRequired, ReviewViewMixin, ActionConfirmMixin, TemplateView +): template_name = "orga/submission/review_delete.html" permission_required = "orga.remove_review" @@ -673,9 +679,15 @@ def post(self, request, *args, **kwargs): return redirect(self.submission.orga_urls.reviews) -class RegenerateDecisionMails(EventPermissionRequired, TemplateView): - template_name = "orga/review/regenerate_decision_mails.html" +class RegenerateDecisionMails( + EventPermissionRequired, ActionConfirmMixin, TemplateView +): permission_required = "orga.change_submissions" + action_title = _("Regenerate decision emails") + action_confirm_label = _("Regenerate decision emails") + action_confirm_color = "success" + action_confirm_icon = "envelope" + action_object_name = "" def get_queryset(self): return ( diff --git a/src/pretalx/orga/views/speaker.py b/src/pretalx/orga/views/speaker.py index e1ff12e64..a9e0a6b9f 100644 --- a/src/pretalx/orga/views/speaker.py +++ b/src/pretalx/orga/views/speaker.py @@ -15,6 +15,7 @@ from pretalx.common.text.phrases import phrases from pretalx.common.views import CreateOrUpdateView from pretalx.common.views.mixins import ( + ActionConfirmMixin, ActionFromUrl, EventPermissionRequired, Filterable, @@ -230,11 +231,23 @@ def get_form_kwargs(self): return kwargs -class SpeakerPasswordReset(SpeakerViewMixin, DetailView): +class SpeakerPasswordReset(SpeakerViewMixin, ActionConfirmMixin, DetailView): permission_required = "orga.change_speaker" - template_name = "orga/speaker/reset_password.html" model = User context_object_name = "speaker" + action_confirm_icon = "key" + action_confirm_label = phrases.base.password_reset_heading + action_title = phrases.base.password_reset_heading + action_text = _( + "Do your really want to reset this user’s password? They won’t be able to log in until they set a new password." + ) + + def action_object_name(self): + user = self.get_object() + return f"{user.get_display_name()} ({user.email})" + + def action_back_url(self): + return self.get_object().event_profile(self.request.event).orga_urls.base def post(self, request, *args, **kwargs): user = self.get_object() @@ -310,10 +323,15 @@ def get_success_url(self): return self.request.event.orga_urls.information -class InformationDelete(PermissionRequired, DetailView): +class InformationDelete(PermissionRequired, ActionConfirmMixin, DetailView): model = SpeakerInformation permission_required = "orga.change_information" - template_name = "orga/speaker/information_delete.html" + + def action_object_name(self): + return _("Speaker information note") + f": {self.get_object().title}" + + def action_back_url(self): + return self.request.event.orga_urls.information def get_queryset(self): return self.request.event.information.all() diff --git a/src/pretalx/orga/views/submission.py b/src/pretalx/orga/views/submission.py index e997da768..faf6b8752 100644 --- a/src/pretalx/orga/views/submission.py +++ b/src/pretalx/orga/views/submission.py @@ -20,14 +20,7 @@ from django.utils.timezone import now from django.utils.translation import gettext as _ from django.utils.translation import override -from django.views.generic import ( - DetailView, - FormView, - ListView, - TemplateView, - UpdateView, - View, -) +from django.views.generic import FormView, ListView, TemplateView, UpdateView, View from django_context_decorator import context from pretalx.agenda.permissions import is_submission_visible @@ -36,6 +29,7 @@ from pretalx.common.urls import build_absolute_uri from pretalx.common.views import CreateOrUpdateView from pretalx.common.views.mixins import ( + ActionConfirmMixin, ActionFromUrl, EventPermissionRequired, PaginationMixin, @@ -1023,13 +1017,18 @@ def form_valid(self, form): return result -class TagDelete(PermissionRequired, DetailView): +class TagDelete(PermissionRequired, ActionConfirmMixin, TemplateView): permission_required = "orga.remove_tags" - template_name = "orga/submission/tag_delete.html" def get_object(self): return get_object_or_404(self.request.event.tags, pk=self.kwargs.get("pk")) + def action_object_name(self): + return _("Tag") + f": {self.get_object().tag}" + + def action_back_url(self): + return self.request.event.orga_urls.tags + def post(self, request, *args, **kwargs): tag = self.get_object() From 9298550657e92b2bb5cee4585063e8e9230b654b Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 3 May 2024 16:30:57 +0200 Subject: [PATCH 044/195] wip --- .github/workflows/strings.yml | 5 ++++- src/pretalx/common/plugins.py | 2 +- src/pretalx/event/stages.py | 3 ++- .../orga/templates/orga/admin/user_detail.html | 2 +- .../orga/templates/orga/cfp/access_code_view.html | 2 +- .../orga/templates/orga/cfp/submission_type_form.html | 2 +- .../orga/templates/orga/cfp/submission_type_view.html | 2 +- src/pretalx/orga/templates/orga/event_list.html | 4 ++-- .../orga/templates/orga/includes/pagination_size.html | 2 +- .../orga/templates/orga/mails/_placeholder_group.html | 8 ++++++-- .../orga/mails/compose_session_mail_form.html | 2 +- .../orga/templates/orga/mails/outbox_list.html | 4 +--- src/pretalx/orga/templates/orga/organiser/list.html | 10 +++++----- .../orga/templates/orga/schedule/room_form.html | 2 +- src/pretalx/orga/templates/orga/speaker/form.html | 11 ++++------- src/pretalx/orga/templates/orga/submission/list.html | 4 +--- .../orga/templates/orga/submission/review.html | 2 +- 17 files changed, 34 insertions(+), 33 deletions(-) diff --git a/.github/workflows/strings.yml b/.github/workflows/strings.yml index 365809914..8ce673772 100644 --- a/.github/workflows/strings.yml +++ b/.github/workflows/strings.yml @@ -30,7 +30,10 @@ jobs: sudo apt update sudo apt install gettext - name: Install Python dependencies - run: python -m pip install -e ".[dev]" + run: python -m pip install -e ".[dev]" polib - name: Compile locales run: python manage.py compilemessages working-directory: ./src + - name: Check translation health + run: python ../.github/workflows/scripts/strings.py + working-directory: ./src diff --git a/src/pretalx/common/plugins.py b/src/pretalx/common/plugins.py index 424a58ee5..7a05099ea 100644 --- a/src/pretalx/common/plugins.py +++ b/src/pretalx/common/plugins.py @@ -12,7 +12,7 @@ "EXPORTER": _("Exporters"), "RECORDING": _("Recording integrations"), "LANGUAGE": _("Languages"), - "OTHER": pgettext_lazy("Type of plugin", "Other"), + "OTHER": pgettext_lazy("category of items", "Other"), } diff --git a/src/pretalx/event/stages.py b/src/pretalx/event/stages.py index ed9f93b94..ece834cb8 100644 --- a/src/pretalx/event/stages.py +++ b/src/pretalx/event/stages.py @@ -2,6 +2,7 @@ from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ +from django.utils.translation import ngettext_lazy from pretalx.common.text.phrases import phrases from pretalx.submission.models import SubmissionStates @@ -94,7 +95,7 @@ def _is_in_wrapup(event): ], }, "EVENT": { - "name": _("Event"), + "name": ngettext_lazy("Event", "Events", 1), "method": _is_running, "icon": "play", "links": [ diff --git a/src/pretalx/orga/templates/orga/admin/user_detail.html b/src/pretalx/orga/templates/orga/admin/user_detail.html index fc00b6e18..a3ec250b0 100644 --- a/src/pretalx/orga/templates/orga/admin/user_detail.html +++ b/src/pretalx/orga/templates/orga/admin/user_detail.html @@ -94,7 +94,7 @@

    {% translate "Proposals" %}

    {% translate "Title" %} - {% translate "Event" %} + {% blocktranslate trimmed count count=1 %}Event{% plural %}Events{% endblocktranslate %} {% translate "State" %} diff --git a/src/pretalx/orga/templates/orga/cfp/access_code_view.html b/src/pretalx/orga/templates/orga/cfp/access_code_view.html index 17bb7d812..987d124d2 100644 --- a/src/pretalx/orga/templates/orga/cfp/access_code_view.html +++ b/src/pretalx/orga/templates/orga/cfp/access_code_view.html @@ -30,7 +30,7 @@

    {% translate "Access codes" %}

    {% translate "Code" %} {% if request.event.feature_flags.use_tracks %}{% translate "Track" %}{% endif %} - {% translate "Session Type" %} + {% translate "Session type" %} {% translate "Uses" %} diff --git a/src/pretalx/orga/templates/orga/cfp/submission_type_form.html b/src/pretalx/orga/templates/orga/cfp/submission_type_form.html index f6c65b92c..a68dcd0b2 100644 --- a/src/pretalx/orga/templates/orga/cfp/submission_type_form.html +++ b/src/pretalx/orga/templates/orga/cfp/submission_type_form.html @@ -16,7 +16,7 @@ {% block cfp_content %}

    {% if form.instance.name %} - {% translate "Session Type" %}: {{ form.instance.name }} + {% translate "Session type" %}: {{ form.instance.name }} {% else %} {% translate "New Session Type" %} {% endif %} diff --git a/src/pretalx/orga/templates/orga/cfp/submission_type_view.html b/src/pretalx/orga/templates/orga/cfp/submission_type_view.html index b8c8178ad..4be03715b 100644 --- a/src/pretalx/orga/templates/orga/cfp/submission_type_view.html +++ b/src/pretalx/orga/templates/orga/cfp/submission_type_view.html @@ -2,7 +2,7 @@ {% load i18n %} {% block cfp_content %} -

    {% translate "Session Types" %}

    +

    {% translate "Session types" %}

    {% blocktranslate trimmed %} Different session types may help to guide speakers into different slot diff --git a/src/pretalx/orga/templates/orga/event_list.html b/src/pretalx/orga/templates/orga/event_list.html index e9124aadf..8ddca587f 100644 --- a/src/pretalx/orga/templates/orga/event_list.html +++ b/src/pretalx/orga/templates/orga/event_list.html @@ -39,7 +39,7 @@

    {{ event.name }}

    {% endif %}
    {% if event.is_public %} -
    {% translate "Live" %}
    +
    {% translate "live" %}
    {% else %}
    {% translate "Not public" %}
    {% endif %} @@ -76,7 +76,7 @@

    {{ event.name }}

    {% if event.is_public %}
    - {% if event.is_open %}{% translate "CfP open" %}{% else %}{% translate "Live" %}{% endif %} + {% if event.is_open %}{% translate "CfP open" %}{% else %}{% translate "live" %}{% endif %}
    {% else %}
    {% translate "Not public" %}
    diff --git a/src/pretalx/orga/templates/orga/includes/pagination_size.html b/src/pretalx/orga/templates/orga/includes/pagination_size.html index 213c4b31d..a8112e3ee 100644 --- a/src/pretalx/orga/templates/orga/includes/pagination_size.html +++ b/src/pretalx/orga/templates/orga/includes/pagination_size.html @@ -5,7 +5,7 @@ href="?{% url_replace request "page_size" pagination_size "page" "1" %}" {% if page_size == pagination_size %}class="font-weight-bold"{% endif %}> {% if pagination_size == 100_000 %} - {% translate "All" %} + {{ phrases.base.all_choices }} {% else %} {{ pagination_size }} {% endif %} diff --git a/src/pretalx/orga/templates/orga/mails/_placeholder_group.html b/src/pretalx/orga/templates/orga/mails/_placeholder_group.html index ce4b671b3..2ef4bc6f5 100644 --- a/src/pretalx/orga/templates/orga/mails/_placeholder_group.html +++ b/src/pretalx/orga/templates/orga/mails/_placeholder_group.html @@ -11,9 +11,13 @@ {% elif tag == "user" %} {% translate "User" %} {% elif tag == "event" %} - {% translate "Event" %} + {% blocktranslate trimmed count count=1 %} + Event + {% plural %} + Events + {% endblocktranslate %} {% elif tag == "other" %} - {% translate "Other" %} + {% translate "Other" context "category of items" %} {% else %} {{ tag }} {% endif %} diff --git a/src/pretalx/orga/templates/orga/mails/compose_session_mail_form.html b/src/pretalx/orga/templates/orga/mails/compose_session_mail_form.html index 55958b59a..0d546a0f9 100644 --- a/src/pretalx/orga/templates/orga/mails/compose_session_mail_form.html +++ b/src/pretalx/orga/templates/orga/mails/compose_session_mail_form.html @@ -40,7 +40,7 @@
    diff --git a/src/pretalx/orga/templates/orga/schedule/room_form.html b/src/pretalx/orga/templates/orga/schedule/room_form.html index eb1eb0060..0333cf320 100644 --- a/src/pretalx/orga/templates/orga/schedule/room_form.html +++ b/src/pretalx/orga/templates/orga/schedule/room_form.html @@ -13,7 +13,7 @@
    {% csrf_token %} {% bootstrap_form_errors form %} -

    {% translate "Room" context "Room information" %}

    +

    {% translate "Room" %}

    {% bootstrap_field form.name layout='event' %} {% bootstrap_field form.description layout='event' %} {% bootstrap_field form.speaker_info layout='event' %} diff --git a/src/pretalx/orga/templates/orga/speaker/form.html b/src/pretalx/orga/templates/orga/speaker/form.html index 3f1080d9b..796434259 100644 --- a/src/pretalx/orga/templates/orga/speaker/form.html +++ b/src/pretalx/orga/templates/orga/speaker/form.html @@ -31,11 +31,12 @@ {% has_perm 'orga.send_mails' request.user request.event as can_send_mails %}

    {{ form.instance.user.get_display_name }} ({{ submissions.count }} - {% blocktranslate trimmed count count=submissions.count %} + {% blocktranslate asvar proposal_title trimmed count count=submissions.count %} proposal {% plural %} proposals - {% endblocktranslate %}) + {% endblocktranslate %} + {{ proposal_title }} {% if can_send_mails %} @@ -46,11 +47,7 @@

    - +
    {{ submission.submission_type }}
    From bb6f35b9c2b790a284a2f4f52800d3e81e5caff2 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Sun, 5 May 2024 21:41:18 +0200 Subject: [PATCH 045/195] Fix incorrect template location --- .../includes/submission_resources_form.html | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 src/pretalx/cfp/templates/includes/submission_resources_form.html diff --git a/src/pretalx/cfp/templates/includes/submission_resources_form.html b/src/pretalx/cfp/templates/includes/submission_resources_form.html deleted file mode 100644 index 87982f95b..000000000 --- a/src/pretalx/cfp/templates/includes/submission_resources_form.html +++ /dev/null @@ -1,93 +0,0 @@ -{% load bootstrap4 %} -{% load i18n %} - -
    - {{ formset.management_form }} - {% bootstrap_formset_errors formset %} - - - {% if can_edit %} - - {% if action != 'view' %} -
    - -
    - {% endif %} - {% endif %} -
    From 03c52ba69fd148a59ea8c2a76d3a7029974e4329 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Mon, 6 May 2024 11:46:47 +0200 Subject: [PATCH 046/195] Fix bug with talk warnings again ref #1494 --- src/pretalx/schedule/models/schedule.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pretalx/schedule/models/schedule.py b/src/pretalx/schedule/models/schedule.py index e15b1fbe0..7ac9d827d 100644 --- a/src/pretalx/schedule/models/schedule.py +++ b/src/pretalx/schedule/models/schedule.py @@ -441,6 +441,7 @@ def get_talk_warnings( models.Q(start__lt=talk.start, end__gt=talk.start) | models.Q(start__lt=talk.real_end, end__gt=talk.real_end) | models.Q(start__gt=talk.start, end__lt=talk.real_end) + | models.Q(start=talk.start, end=talk.real_end) ) .exists() ) From bad272e62e0ad8e7c07a94f32397eb094edcc48f Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 7 May 2024 11:57:36 +0200 Subject: [PATCH 047/195] Load missing templatetag --- .../agenda/templates/agenda/includes/submission_resource.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pretalx/agenda/templates/agenda/includes/submission_resource.html b/src/pretalx/agenda/templates/agenda/includes/submission_resource.html index bd0125b09..306e8efa4 100644 --- a/src/pretalx/agenda/templates/agenda/includes/submission_resource.html +++ b/src/pretalx/agenda/templates/agenda/includes/submission_resource.html @@ -1,3 +1,4 @@ +{% load filesize %} {{ resource.description }} From 02079b0114a9a6649b1a49efad066b2b9c0b1007 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 7 May 2024 11:58:56 +0200 Subject: [PATCH 048/195] Make ActivityLog.json_data safe --- src/pretalx/common/models/log.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pretalx/common/models/log.py b/src/pretalx/common/models/log.py index ab923e338..49e5d6d01 100644 --- a/src/pretalx/common/models/log.py +++ b/src/pretalx/common/models/log.py @@ -1,5 +1,6 @@ import json import logging +from contextlib import suppress from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType @@ -53,7 +54,8 @@ def __str__(self): @cached_property def json_data(self): if self.data: - return json.loads(self.data) + with suppress(json.JSONDecodeError): + return json.loads(self.data) return {} @cached_property From 57810aebe9eeb63159835661adeb8c8a85101da2 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 7 May 2024 12:03:25 +0200 Subject: [PATCH 049/195] Fix template name typo --- .../cfp/templates/cfp/event/user_submission_invitation.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pretalx/cfp/templates/cfp/event/user_submission_invitation.html b/src/pretalx/cfp/templates/cfp/event/user_submission_invitation.html index 12efaa0de..48b2da198 100644 --- a/src/pretalx/cfp/templates/cfp/event/user_submission_invitation.html +++ b/src/pretalx/cfp/templates/cfp/event/user_submission_invitation.html @@ -5,7 +5,7 @@ {% block title %}{{ submission.title }} :: {% endblock %} {% block content %} - {% include "cfp/includs/user_submission_header.html" %} + {% include "cfp/includes/user_submission_header.html" %}
    {% blocktranslate trimmed %} Invite another speaker to your proposal here. Instead of letting us send an email, From fa9362a6f1a720791c1b0881c82c633aeb152fbc Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 7 May 2024 16:03:48 +0200 Subject: [PATCH 050/195] Fix bug with overlap detection --- src/pretalx/schedule/models/schedule.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pretalx/schedule/models/schedule.py b/src/pretalx/schedule/models/schedule.py index 7ac9d827d..f64951b4f 100644 --- a/src/pretalx/schedule/models/schedule.py +++ b/src/pretalx/schedule/models/schedule.py @@ -437,6 +437,7 @@ def get_talk_warnings( TalkSlot.objects.filter( schedule=self, submission__speakers__in=[speaker] ) + .exclude(pk=talk.pk) .filter( models.Q(start__lt=talk.start, end__gt=talk.start) | models.Q(start__lt=talk.real_end, end__gt=talk.real_end) From 0f924300da0bf53ac929d9de315826219863125e Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 7 May 2024 16:40:15 +0200 Subject: [PATCH 051/195] Add missing questions form --- src/pretalx/orga/templates/orga/submission/content.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pretalx/orga/templates/orga/submission/content.html b/src/pretalx/orga/templates/orga/submission/content.html index 2c7455edf..6b31c8655 100644 --- a/src/pretalx/orga/templates/orga/submission/content.html +++ b/src/pretalx/orga/templates/orga/submission/content.html @@ -86,6 +86,10 @@ {% bootstrap_field form.duration addon_after='minutes' layout='event' addon_after_class="input-group-append input-group-text" %} {% if form.slot_count %}{% bootstrap_field form.slot_count layout='event' %}{% endif %} {% if form.image %}{% bootstrap_field form.image layout='event' %}{% endif %} + {% if questions_form and questions_form.fields %} +
    {% translate "Questions" %}
    + {% bootstrap_form questions_form layout='event' %} + {% endif %} {% if action != 'create' %} {% include "cfp/includes/submission_resources_form.html" %} {% endif %} From c885777013de49d1a26875628b6cfa76f489532e Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 7 May 2024 18:05:16 +0200 Subject: [PATCH 052/195] Fix template name --- src/pretalx/cfp/templates/cfp/event/user_submission_edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html b/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html index 3124ea0a1..a6b05a43a 100644 --- a/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html +++ b/src/pretalx/cfp/templates/cfp/event/user_submission_edit.html @@ -75,7 +75,7 @@
    {% bootstrap_form form layout='event' %} {% bootstrap_form qform layout='event' %} {% if can_edit or form.instance.resources.count %} - {% include "cfp/includes/submissioN_resources_form.html" %} + {% include "cfp/includes/submission_resources_form.html" %} {% endif %} {% if can_edit %}
    From 9db0f7ca3295e3981bb0be12cbd86f34d032dbc0 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 7 May 2024 18:50:09 +0200 Subject: [PATCH 053/195] Fix some breaking tests --- src/pretalx/orga/views/organiser.py | 3 +-- src/tests/agenda/views/test_agenda_schedule.py | 4 ++-- src/tests/orga/views/test_orga_views_event.py | 5 +++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pretalx/orga/views/organiser.py b/src/pretalx/orga/views/organiser.py index e0065064d..a6b2dfb85 100644 --- a/src/pretalx/orga/views/organiser.py +++ b/src/pretalx/orga/views/organiser.py @@ -163,9 +163,8 @@ def object(self): ) -class TeamUninvite(InviteMixin, PermissionRequired, DetailView): +class TeamUninvite(InviteMixin, PermissionRequired, ActionConfirmMixin, DetailView): model = TeamInvite - template_name = "orga/settings/team_delete.html" permission_required = "orga.change_teams" action_title = _("Retract invitation") action_text = _("Are you sure you want to retract the invitation to this user?") diff --git a/src/tests/agenda/views/test_agenda_schedule.py b/src/tests/agenda/views/test_agenda_schedule.py index 6fe4a828a..50a70c690 100644 --- a/src/tests/agenda/views/test_agenda_schedule.py +++ b/src/tests/agenda/views/test_agenda_schedule.py @@ -134,7 +134,7 @@ def test_speaker_page( other_submission.slots.all().update(is_visible=True) slot.submission.slots.all().update(is_visible=True) url = reverse("agenda:speaker", kwargs={"code": speaker.code, "event": event.slug}) - with django_assert_num_queries(23): + with django_assert_num_queries(24): response = client.get(url, follow=True) assert response.status_code == 200 assert len(response.context["talks"]) == 2, response.context["talks"] @@ -165,7 +165,7 @@ def test_speaker_page_other_submissions_only_if_visible( ) url = reverse("agenda:speaker", kwargs={"code": speaker.code, "event": event.slug}) - with django_assert_num_queries(18): + with django_assert_num_queries(19): response = client.get(url, follow=True) assert response.status_code == 200 diff --git a/src/tests/orga/views/test_orga_views_event.py b/src/tests/orga/views/test_orga_views_event.py index 8851a6d9c..27410c163 100644 --- a/src/tests/orga/views/test_orga_views_event.py +++ b/src/tests/orga/views/test_orga_views_event.py @@ -995,6 +995,11 @@ def test_edit_review_settings_delete_review_phase(orga_client, event): response = orga_client.post(phase.urls.delete, follow=True) assert response.status_code == 200 event = Event.objects.get(slug=event.slug) + with scope(event=event): + assert event.review_phases.count() == 2 + response = orga_client.post(phase.urls.delete, follow=True) + assert response.status_code == 200 + event = Event.objects.get(slug=event.slug) with scope(event=event): assert event.review_phases.count() == 1 From 26bff248fd94907e0af1869f8cfac26ab169fc3a Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Wed, 15 May 2024 17:19:19 +0200 Subject: [PATCH 054/195] Fix deletion of draft proposals with resources --- src/pretalx/submission/models/submission.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pretalx/submission/models/submission.py b/src/pretalx/submission/models/submission.py index 552fe9165..2d5ca5251 100644 --- a/src/pretalx/submission/models/submission.py +++ b/src/pretalx/submission/models/submission.py @@ -707,6 +707,8 @@ def delete(self, force: bool = False, **kwargs): ) for answer in self.answers.all(): answer.delete() + for resource in self.resources.all(): + resource.delete() super().delete(**kwargs) @cached_property From bf994cb5884679ce22b273f53cda74bcdd5dca66 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Wed, 15 May 2024 17:19:59 +0200 Subject: [PATCH 055/195] Simplify proposal deletion --- src/pretalx/submission/models/submission.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pretalx/submission/models/submission.py b/src/pretalx/submission/models/submission.py index 2d5ca5251..bf261be0d 100644 --- a/src/pretalx/submission/models/submission.py +++ b/src/pretalx/submission/models/submission.py @@ -705,10 +705,8 @@ def delete(self, force: bool = False, **kwargs): raise SubmissionError( "Submission is not in draft mode and cannot be deleted completely. Set the deleted flag instead." ) - for answer in self.answers.all(): - answer.delete() - for resource in self.resources.all(): - resource.delete() + self.answers.all().delete() + self.resources.all().delete() super().delete(**kwargs) @cached_property From 17d8367dbe48ea88c7eb4e9dce4c423edbfc3cf9 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 11:47:27 +0200 Subject: [PATCH 056/195] Better fallbacks on missing review phases --- src/pretalx/event/models/event.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pretalx/event/models/event.py b/src/pretalx/event/models/event.py index d556d81de..776bac1e0 100644 --- a/src/pretalx/event/models/event.py +++ b/src/pretalx/event/models/event.py @@ -939,12 +939,13 @@ def reviews(self): @cached_property def active_review_phase(self): - phase = self.review_phases.filter(is_active=True).first() - if not phase and not self.review_phases.all().exists(): + if phase := self.review_phases.filter(is_active=True).first(): + return phase + if not self.review_phases.all().exists(): from pretalx.submission.models import ReviewPhase cfp_deadline = self.cfp.deadline - phase = ReviewPhase.objects.create( + return ReviewPhase.objects.create( event=self, name=_("Review"), start=cfp_deadline, @@ -953,7 +954,7 @@ def active_review_phase(self): can_see_other_reviews="after_review", can_see_speaker_names=True, ) - return phase + return self.update_review_phase() def update_review_phase(self): """This method activates the next review phase if the current one is From 8d5023f8bf85d9ce577abcb85a05075136b367da Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 11:49:23 +0200 Subject: [PATCH 057/195] Fix proposal count in filtered speaker list closes #1768 --- doc/changelog.rst | 3 +++ src/pretalx/orga/views/speaker.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/doc/changelog.rst b/doc/changelog.rst index cde01b47a..1969c6608 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -8,6 +8,7 @@ Release Notes <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD - :feature:`schedule,1794` The iCal schedule export has been made private (available only to organisers) as the utility of importing a conference's entire schedule is limited, and people were frustrated that the iCal export did not reflect any applied schedule filters. - :bug:`schedule,1803` The QR code for schedule exporter links was not showing up when hovering on the QR code symbol. - :release:`2024.2.1 <2024-08-07>` @@ -17,6 +18,8 @@ Release Notes - :bug:`orga` The markdown preview posed a security vulnerability by allowing speakers and organisers to include unsafe JavaScript. This JavaScript would only be executed when accessing the preview, i.e. when a speaker or organiser opened to proposal page (not attendees or the public). Thanks to Jorian Woltjer for reporting this issue. - :feature:`api` The submission API now has a filter for the ``is_featured`` field. - :feature:`cfp,1761` In the CfP submission multi-step form, the tab title now reflects the proposal title, to make it easier to work on multiple proposal submissions at the same time. +======= +>>>>>>> f8d800d83 (Fix proposal count in filtered speaker list) - :bug:`orga:speaker,1768` When filtering the speaker list by only accepted/confirmed speakers, the listed proposal count would be incorrect (inflated). - :feature:`cfp,1574` pretalx now supports the ``~~`` strikethrough syntax in Markdown. - :bug:`orga:schedule,1702` Sessions starting at exactly midnight of the first day of the event would not show up in the schedule editor (but could be scheduled there by dropping them on the day heading). diff --git a/src/pretalx/orga/views/speaker.py b/src/pretalx/orga/views/speaker.py index a9e0a6b9f..dce8dda9a 100644 --- a/src/pretalx/orga/views/speaker.py +++ b/src/pretalx/orga/views/speaker.py @@ -82,11 +82,13 @@ def get_queryset(self): submission_count=Count( "user__submissions", filter=Q(user__submissions__event=self.request.event), + distinct=True, ), accepted_submission_count=Count( "user__submissions", filter=Q(user__submissions__event=self.request.event) & Q(user__submissions__state__in=SubmissionStates.accepted_states), + distinct=True, ), ) ) From 6dc59af39f01c44cf9818a86c1acfdb145e8fa98 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 12:29:53 +0200 Subject: [PATCH 058/195] Show misspelled words in action summary --- doc/Makefile | 2 +- doc/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index 210ce3bf3..bd03f825a 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -180,4 +180,4 @@ spelling: $(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling @echo @echo "Spelling check finished, look at the results in " \ - "$(BUILDDIR)/spelling/output.txt." + "$(BUILDDIR)/spelling/*." diff --git a/doc/conf.py b/doc/conf.py index 15dbce851..b5cbd8dd9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -136,7 +136,7 @@ if HAS_PYENCHANT: spelling_lang = 'en_GB' spelling_word_list_filename='spelling_wordlist.txt' - spelling_show_suggestions=True + spelling_show_suggestions=False # Copybutton options copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: |# |\(env\)\$ " From 1a238b73264f62be78c41226a32a15a5e809d6f1 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 12:30:16 +0200 Subject: [PATCH 059/195] Show failed linkchecks in actions summary --- .github/workflows/docs.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 44d888e2c..c39820b4a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -64,3 +64,10 @@ jobs: - name: Linkcheck docs run: make linkcheck working-directory: ./doc + - name: Put linkcheck result into summary + run: | + echo "## Linkcheck results" >> $GITHUB_STEP_SUMMARY + sed 's/^/- /' < _build/linkcheck/output.txt >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + working-directory: ./doc + if: always() From c07e9da52e00e5b90a1a8960d419951006449826 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 12:51:34 +0200 Subject: [PATCH 060/195] Fix spellings --- doc/administrator/configure.rst | 2 +- doc/administrator/installation.rst | 8 +-- doc/api/resources/tags.rst | 2 +- doc/changelog.rst | 80 ++++++++++++++-------------- doc/developer/plugins/customview.rst | 7 +-- doc/developer/plugins/exporter.rst | 5 +- doc/developer/plugins/plugins.rst | 16 +++--- doc/developer/setup.rst | 2 +- doc/developer/translating.rst | 2 +- doc/maintainer/release.rst | 6 +-- doc/spelling_wordlist.txt | 22 +++++++- 11 files changed, 87 insertions(+), 65 deletions(-) diff --git a/doc/administrator/configure.rst b/doc/administrator/configure.rst index 36cc41221..04b23ed0a 100644 --- a/doc/administrator/configure.rst +++ b/doc/administrator/configure.rst @@ -139,7 +139,7 @@ The database section - **Default:** ``sqlite3`` +------------+----------------------+-----------------------+ -| Database | Configuration string | pip packag e | +| Database | Configuration string | pip package | +============+======================+=======================+ | PostgresQL | ``postgresql`` | ``pretalx[postgres]`` | +------------+----------------------+-----------------------+ diff --git a/doc/administrator/installation.rst b/doc/administrator/installation.rst index 5c25dcc44..0cec29d57 100644 --- a/doc/administrator/installation.rst +++ b/doc/administrator/installation.rst @@ -214,9 +214,9 @@ You can now run the following commands to enable and start the services:: Step 7: Reverse proxy --------------------- -You’ll need to set up an HTTP reverse proxy to handle HTTPS connections. It doesn’t -particularly matter which one you use, as long as you make sure to use `strong -encryption settings`_. Your proxy should +You’ll need to set up an HTTP reverse proxy to handle HTTPS connections. It +does not particularly matter which one you use, as long as you make sure to use +`strong encryption settings`_. Your proxy should * serve all requests exclusively over HTTPS, * follow established security practices regarding protocols and ciphers. @@ -253,7 +253,7 @@ Step 9: Provide periodic tasks ------------------------------ There are a couple of things in pretalx that should be run periodically. It -doesn’t matter how you run them, so you can go with your choice of periodic +does not matter how you run them, so you can go with your choice of periodic tasks, be they systemd timers, cron, or something else entirely. In the same environment as you ran the previous pretalx commands (e.g. the diff --git a/doc/api/resources/tags.rst b/doc/api/resources/tags.rst index 3431fa16d..5253c3bba 100644 --- a/doc/api/resources/tags.rst +++ b/doc/api/resources/tags.rst @@ -16,7 +16,7 @@ Field Type Description ===================================== ========================== ======================================================= tag string The actual tag name. description multi-lingual string The description of the tag. -color string The tag’s color as hex string. +``color`` string The tag’s colour as hex string. ===================================== ========================== ======================================================= Endpoints diff --git a/doc/changelog.rst b/doc/changelog.rst index 1969c6608..f95a0b079 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -63,7 +63,7 @@ Release Notes - :feature:`orga:email,1351` pretalx now provides two new email placeholders, ``speaker_schedule_new`` (all talks changed in the current schedule, like in the notification email generated on schedule release) and ``speaker_schedule_full`` (a list of **all** scheduled sessions for that user). - :bug:`schedule,1666` When building the social media preview card, pretalx would display a session’s description rather than its abstract. - :bug:`cfp,1660` When setting character limits on text questions, pretalx would count line breaks as two characters. -- :bug:`orga:submission,1613` The session page dropdown would sometimes include the "public link" even though it wasn’t public yet. This has been fixed, and in the case of accepted or confirmed proposals that don’t have a public page yet (e.g. because they’re not scheduled yet), the link is marked as "public link (not public yet)". +- :bug:`orga:submission,1613` The session page drop-down would sometimes include the "public link" even though it was not public yet. This has been fixed, and in the case of accepted or confirmed proposals that don’t have a public page yet (e.g. because they’re not scheduled yet), the link is marked as "public link (not public yet)". - :bug:`cfp` When rendering email addresses in Markdown, shorter TLDs won out against longer ones (e.g. .co instead of .com, or .ro instead of .rocks). This was already fixed for normal links, just not for email addresses. Until you update to include this fix, you can instead turn emails into a link yourself: ``[test@example.co](mailto:test@example.co)``. - :feature:`orga,1619` Organisers can now add new team members in bulk instead of one by one. - :feature:`orga:schedule,1587` A hint now shows when users click the "New break" box, informing them that they have to drag it to the schedule instead. @@ -91,8 +91,8 @@ Release Notes - :feature:`dev` Plugins can now also render nested menu entries in the sidebar navigation. - :feature:`orga,1107` pretalx now warns users when they try to leave a page with unsaved changes. - :feature:`cfp,1107` pretalx now warns users when they try to leave a page with unsaved changes. -- :feature:`schedule,1041` Talks can now be faved (added to the list of favourited talks) from the talk page, not just from the schedule page. -- :feature:`dev` Plugins can now declare their category, which is used to group them in the plugin list. Available categories are "Feature", "Integration", "Customization", "Exporter", "Recording", "Language" and "Other". Plugins without a set category will be grouped as "Other". If you’re a plugin developer, please update your plugin to declare a category! +- :feature:`schedule,1041` Talks can now be starred (added to the list of favourite talks) from the talk page, not just from the schedule page. +- :feature:`dev` Plugins can now declare their category, which is used to group them in the plugin list. Available categories are "Feature", "Integration", "Customisation", "Exporter", "Recording", "Language" and "Other". Plugins without a set category will be grouped as "Other". If you’re a plugin developer, please update your plugin to declare a category! - :feature:`admin,1588` Administrators can now see their pretalx version in the admin dashboard. - :bug:`schedule` RSS feeds for new releases would sometimes fail to load if control characters were used in the schedule version or event name. - :bug:`cfp` Draft proposals could not be discarded if they included answered questions. @@ -109,30 +109,30 @@ Release Notes - :bug:`lang` For users without a pretalx account, their browser’s choice of language took precedence over their own language selection. - :bug:`lang` When using non-standard languages, pretalx would only show those languages as available sometimes. - :bug:`admin,1579` There was a bug in the `pretalx init` command, and also too verbose output. -- :bug:`orga,1577` The printable proposal cards showed broken characters for anything outside latin1. +- :bug:`orga,1577` The printable proposal cards showed broken characters for anything outside Latin1. - :bug:`orga` Reordering questions while some of them were inactive could lead to 404 errors. -- :bug:`orga:submission` pretalx wasn’t able to filter pending state changes from the organiser proposal list. -- :bug:`lang` The schedule editor was not operational with some languages, particularly with different language code versions (pt-BR vs pt_br). +- :bug:`orga:submission` pretalx was not able to filter pending state changes from the organiser proposal list. +- :bug:`lang` The schedule editor was not operational with some languages, particularly with different language code versions (e.g. Brazilian versus European Portuguese). - :bug:`orga:schedule` The schedule editor would not show some specific time selectors when people expanded the timeline to see five-minute steps. - :release:`2023.1.0 <2023-08-30>` - :feature:`orga:schedule` Completely rewrote the schedule editor, making it look like the actual schedule, and added some powerful features like hiding rooms, easy duration changes, and more. - :feature:`admin` The ``pretalx init`` command now has a ``--no-input`` flag for all your automation needs. -- :announcement:`admin` pretalx no longer logs 404 responses, as you can get those equally from your webserver logs. +- :announcement:`admin` pretalx no longer logs 404 responses, as you can get those equally from your web server logs. - :feature:`schedule,399` pretalx will now locally cache gravatar avatars to avoid GDPR issues when using gravatar. - :bug:`schedule,1498` Talks that were scheduled, but not confirmed by the speaker yet would be shown in the public speaker profile. - :feature:`orga:review` pretalx always showed the anonymised version of proposals if there was one. Now it reverts to the non-anonymised one once the anonymisation period is over. - :feature:`orga:speaker` Organiser pages for speakers now use their alphanumeric ``code`` identifier in the URL rather than the numeric ID, matching the public and API pages. -- :feature:`orga:submission,1347` The submission type and track lists now include links to the pre-filtered list of proposals. +- :feature:`orga:submission,1347` The submission type and track lists now include links to the filtered list of proposals. - :feature:`cfp,889` A talk’s duration is now listed on the talk acceptance site to avoid misunderstandings early on. - :announcement:`admin` Due to database versions going EOL, please make sure to use PostgreSQL 12+, MySQL 8+, MariaDB 10.4+, or SQLite 3.21. - :announcement:`admin` As Python 3.6 and 3.7 are now EOL, and we are using new Python features, pretalx supports Python versions 3.9+. - :feature:`orga:review` The review dashboard can now be filtered by question answers, just like the submission list. - :feature:`orga:submission` New anonymisation indicator in proposal list. -- :feature:`cfp,1418` Speaker availabilities are now limited to the sum of all room availabilites. +- :feature:`cfp,1418` Speaker availabilities are now limited to the sum of all room availabilities. - :feature:`orga,1440` The list of team members is now always sorted by name. - :announcement:`admin` Document that in nginx, gzip should be turned on only for static files. - :bug:`admin,1098` There was a very rare bug that could lock up pretalx instances due to a race condition in the review process, and required manual admin intervention to get fixed. -- :feature:`orga,1061` Image previews (e.g. for event logos) now handle transparency by adding a checkered background, so even the white logo fans can still see their images. +- :feature:`orga,1061` Image previews (e.g. for event logos) now handle transparency by adding a chequered background, so even the white logo fans can still see their images. - :feature:`orga,963` The featured talks page is now linked in the corresponding setting, making it easier for organisers to find. - :feature:`orga:submission,392` Our longest-standing feature request has finally been closed: You can now set the possible proposal/content languages independently from the available UI languages. - :bug:`cfp,1363` pretalx now shows the actual file upload limit to users uploading resources rather than a slightly too-large one. @@ -141,7 +141,7 @@ Release Notes - :bug:`orga:email` The reject email template was missing on the template list. - :feature:`admin` Administrators can now change event short names in the frontend rather than having to dig into the database. - :feature:`schedule,699` In the emails sent to speakers when their talks change, they will now also receive calendar files for the changed talks. -- :feature:`orga:review,1185` Reviewers will see a checkmark next to talks they have submitted, so they won’t appear like things they should review. +- :feature:`orga:review,1185` Reviewers will see a tick next to talks they have submitted, so they won’t appear like things they should review. - :feature:`orga:review` In the review dashboards, users can now remove and add columns, including the track, session duration and shorter questions. - :feature:`api` The submission API now includes IDs for submission types, tracks and rooms, rather than just references by name. - :feature:`cfp,672` Speakers (or rather submitters) can now save a proposal as a draft while they are working on finishing the submission process. @@ -158,7 +158,7 @@ Release Notes - :feature:`orga:email` To improve email template handling, the list of emails now shows just the subjects or use case, and you can click them to expand and see the details. - :feature:`schedule` Breaks are now also shown on the mobile/minimal/linear schedule. - :bug:`orga:review` Review pages were not working when pretalx was run with Python 3.7 and the aggregation method "mean" (as opposed to "median"). -- :feature:`orga` Teams are now sorted by the date of their accessible events, making it easier to manage organizers with many event-specific teams. +- :feature:`orga` Teams are now sorted by the date of their accessible events, making it easier to manage organisers with many event-specific teams. - :bug:`schedule` The schedule widget was not showing up for some locales (particularly Chinese). - :feature:`schedule` On sessions that have both videos and images, videos now show up first, and the overall layout is improved. - :feature:`orga:schedule` Schedule release warnings are now more actionable, by linking to more problematic proposals directly, or to a page listing all affected proposals for less complex warnings. @@ -166,7 +166,7 @@ Release Notes - :feature:`orga:review` If you limit reviewer teams to specific tracks, they won’t be able to see speaker profiles from outside their track(s) anymore. - :feature:`schedule` Not so much a feature as a change: Speaker images are now cropped to the centre in the speaker list squares instead of to the top. - :bug:`schedule` Fix social media preview images sometimes not showing up due to robots.txt constraints. -- :feature:`schedule` Use speaker profile images as social media preview where possible (does not include gravatar support atm). +- :feature:`schedule` Use speaker profile images as social media preview where possible (does not include gravatar support at the moment). - :feature:`schedule` Header images are now used as fallback for social media preview images if there’s no logo. - :bug:`cfp` Events with per-submission-type questions sometimes saw empty questions pages in the CfP flow. - :feature:`orga:review` Organisers can now assign reviewers to proposals in bulk, by uploading a JSON file. @@ -175,8 +175,8 @@ Release Notes - :feature:`cfp,1301` Following a confirmation link to a proposal you don’t have access to now shows a helpful page prompting you to double-check your account is correct. Anonymous users will be directed to log in first. - :feature:`orga:review` When you sort the review dashboard by number of reviews, it will now only use real reviews, not abstentions. The number of reviews including abstentions will be shown in parentheses. - :bug:`cfp,1307` Availability times provided while confirming a proposal were not saved. -- :feature:`orga:speaker,819` You can now turn off co-speakers – organsiers can still assign additional speakers, but speakers themselves will not be asked for additional speakers. -- :announcement:`admin` Note to administrators of self-hosted instances: documentation for installation and upgrades now recommends that you use ``pip install --upgrade-strategy eager`` to make sure you get non-pinned bugfix updates. +- :feature:`orga:speaker,819` You can now turn off co-speakers – organisers can still assign additional speakers, but speakers themselves will not be asked for additional speakers. +- :announcement:`admin` Note to administrators of self-hosted instances: documentation for installation and upgrades now recommends that you use ``pip install --upgrade-strategy eager`` to make sure you get non-pinned updates. - :feature:`api` Organisers can see speaker email addresses in embedded API paths. - :feature:`orga:submission` Proposal attachments can be included in exports now. - :feature:`orga:review` Organisers can configure how the review score should be displayed to reviewers: only explanation, only score, explanation first, score first. @@ -207,7 +207,7 @@ Release Notes - :feature:`orga:submission` You can now remove a pending state be re-selecting the current state of a proposal. - :feature:`orga:email` Email placeholders now explain their use when you hover over them. - :feature:`orga:email` New email placeholder: ``{all_reviews}`` allows you to send all review texts (though not scores!) to submitters. -- :bug:`orga:schedule,1266` pretalx only recognised overlapping scheduled talks for a speaker when they didn’t start or end at the exact same time. +- :bug:`orga:schedule,1266` pretalx only recognised overlapping scheduled talks for a speaker when they did not start or end at the exact same time. - :feature:`orga` The rendering speed of all backend pages has been improved. - :feature:`orga:schedule` The performance of the schedule editor and release pages was improved for large events. - :bug:`orga:review` The track filter was missing on the review dashboard page. @@ -215,7 +215,7 @@ Release Notes - :feature:`orga` Reviewer team settings (like track assignments) are now on the same page as the general team settings, and will be shown only if the team is currently a reviewer team. - :feature:`orga:review,619` Reviewers can now be assigned to proposals directly. Depending on your settings, reviewers can only see their assigned proposals, or will just see them highlighted. - :feature:`schedule` Caching of schedule pages is reset the moment a new schedule version is released, so that integrations (for example with Venueless) that push notifications on new schedule releases will always see the actual new schedule. -- :feature:`orga:schedule` Schedule pages showing the WIP schedule to organsiers aren’t cached anymore, so all changes show up immediately. +- :feature:`orga:schedule` Schedule pages showing the WIP schedule to organisers are not cached anymore, so all changes show up immediately. - :feature:`orga:speaker,1261` Automatic confirmation emails of received proposals are now also shown in the list of a user’s emails, since the absence was confusing for organisers and speakers. - :bug:`orga,1260` It was possible to change teams so that they had access to no events – neither via the explicit list, nor via the "all events" flag, which was extremely confusing. - :bug:`orga,1259` The organiser dashboard included deleted proposals in the count on the event overview. @@ -226,7 +226,7 @@ Release Notes - :bug:`orga:email,1257` The email editor started to require all languages to be filled in, instead of at least one language. This was unintended, the previous behaviour has been restored. - :feature:`orga:schedule,766` When you change an event’s timezone, all talks will now be moved to appear at the same *local* time. - :bug:`orga:schedule,1248` It was possible to set a talk’s end time before its start time. -- :bug:`schedule,1247` In some cases, individual talk iCalendar files could be empty. +- :bug:`schedule,1247` In some cases, individual talk iCal files could be empty. - :bug:`orga:email,1244` Removed incorrect link to email editor from speaker pages. - :announcement:`admin` With the new ``move_event`` command, you can move events to the current day (default) or any other date, like this: ``move_event --event --date 2021-12-26`` - :release:`2.3.1 <2021-12-26>` @@ -237,14 +237,14 @@ Release Notes - :feature:`api,1232` You can filter submissions by multiple states in the API now. - :announcement:`admin` When updating, please take care to update your plugins, as some interfaces have changed. Plugin authors, please refer to PR 1230 to see changed settings access. - :feature:`orga:review` Tags are now shown in the reviewer dashboard and can be filtered for. -- :feature:`schedule` Pretalx now remembers the timezone you’ve selected on the schedule page across reloads. +- :feature:`schedule` Pretalx now remembers the timezone you have selected on the schedule page across reloads. - :feature:`orga:schedule` The schedule editor now polls changes, so if somebody else changes the schedule while you’re editing it, you will see the changes soon afterwards. - :feature:`orga:schedule` Pretalx will now highlight overlapping sessions on the schedule editor, and will also warn you before you release a new schedule if sessions overlap in the same room. - :bug:`orga:schedule` When you clicked a talk in the schedule editor, it would open in a new window, but also stay in dragging mode in the editor page. - :bug:`orga:email` Fixed an issue when rendering individual session times in emails. - :feature:`schedule` Schedules have better scroll behaviour on very wide and very narrow displays. - :feature:`admin` Media files are now excluded from crawlers via robots.txt. -- :bug:`orga:review` Fixed a bug where abstaining during the review process wasn’t possible while review scores were mandatory. +- :bug:`orga:review` Fixed a bug where abstaining during the review process was not possible while review scores were mandatory. - :feature:`cfp` If you run a multi-lingual event, you don’t have to request the content locale in your CfP anymore. - :feature:`lang` pretalx now comes with new translations, in Arabic, Spanish, and Brazilian Portuguese! - :feature:`orga:email` Email signatures now look a lot better in HTML emails @@ -253,11 +253,11 @@ Release Notes - :feature:`dev` Plugin languages can now be either globally available or only for active events – plugin developers, please adjust your plugins! - :feature:`cfp` Organisers can now disable the optional inclusion of gravatar images. - :feature:`schedule` If you attach ``?lang=en`` to a request, pretalx will serve the page in the requested language (if active in the current event). -- :bug:`orga,1157` When adding a new organisers to a team, email suggestions from known users didn’t work. +- :bug:`orga,1157` When adding a new organisers to a team, email suggestions from known users did not work. - :bug:`orga:submission,1157` When adding a new speaker to a proposal, pretalx would suggest organiser accounts rather than speaker accounts. - :feature:`orga:email,412` pretalx finally supports sending of emails based on templates, with a full template placeholder system. Hello, {name}! - :feature:`orga:email,715` Email filters are now subtractive instead of additive, giving you more fine-grained control about your bulk emails. -- :bug:`orga:email,1150` pretalx now doesn’t allow you to test your custom SMTP settings until you have actually configured them. +- :bug:`orga:email,1150` pretalx now does not allow you to test your custom SMTP settings until you have actually configured them. - :feature:`orga:review,976` Improved the tagging interface to be still useful with a large number of tags. - :feature:`orga:schedule,933` You can now change a session’s room and time in the session form, allowing for minute-level accuracy instead of our usual 5-minute intervals. - :feature:`dev` Plugins can now perform actions on every schedule release (for example, to trigger an update in external consumers to avoid polling). @@ -265,7 +265,7 @@ Release Notes - :feature:`orga:schedule` As a reminder, the event timezone will be shown at the top of the schedule editor page. - :feature:`orga:review` Anonymisation for reviewers can now be switched on on a team level, overriding the general event settings. - :feature:`orga` Plugin selection is now available for all organisers, not just administrators. -- :bug:`schedule` Session detail pages didn’t use the full width of the page. +- :bug:`schedule` Session detail pages did not use the full width of the page. - :feature:`dev` There is a new plugin hook that allows you to perform actions when a new schedule is released. - :release:`2.2.0 <2021-08-15>` - :feature:`schedule` To improve performance, the NoJS schedule is now located on a separate page. @@ -279,15 +279,15 @@ Release Notes - :feature:`cfp,1069` You can freeze a question after a certain date, prohibiting users from changing their answers after the deadline. - :feature:`cfp,1069` You can now attach deadlines to questions, making them optional before the deadline and mandatory afterwards. - :feature:`api` With the ``anon`` query parameter, you can request anonymised proposal data from the API, even when you have permission to see the full data. -- :bug:`cfp` In the CfP editor, when a step description was only given in a language that wasn’t currently active, you couldn’t change it any longer. +- :bug:`cfp` In the CfP editor, when a step description was only given in a language that was not currently active, you could not change it any longer. - :bug:`orga:email,1111` pretalx would send multiple emails for proposals with multiple speakers. - :bug:`orga:review` Not all existing review scores were recalculated when review score weights were changed during a review phase. -- :feature:`schedule,1082` Event header images are now scaled down to a height of 150px. +- :feature:`schedule,1082` Event header images are now scaled down to a height of ``150px``. - :bug:`orga:email,1093` pretalx sometimes over-reported the number of emails generated when bulk-sending emails. - :feature:`orga:submission,1092` You can now get a list of proposals or speakers that are still missing the answer to a given question. - :bug:`schedule` The display of external videos in pretalx was broken due to a security header being set too strictly. - :feature:`schedule` pretalx has better rendering for multi-line code blocks (``\`\`\```) in markdown elements and supports code highlighting. -- :bug:`cfp` When your default submission type had a deadline prior to the event-wide deadline, the CfP form wouldn’t accept new proposals past the earlier deadline. +- :bug:`cfp` When your default submission type had a deadline prior to the event-wide deadline, the CfP form would not accept new proposals past the earlier deadline. - :bug:`orga:schedule,1087` pretalx would sometimes show unnecessary warnings in the talk editor when talks were scheduled across day breaks. - :feature:`orga:review` You can mark review score categories as independent. They won’t be part of the total calculation, and instead show up as their own column in the review dashboard. - :feature:`orga:speaker` You can now search speakers by specific given answers, as you could already search proposals and sessions. @@ -298,7 +298,7 @@ Release Notes - :feature:`orga:submission,1047` The review statistics timeline chart now includes the total submitted proposals to the given date, in addition to the proposals submitted on the given date. - :bug:`orga:review,1049` Reviewers without further permissions could not create tags, even when they had the necessary permissions. - :feature:`schedule,1036` The talk feedback page is now available once a talk has started, not once it is over. -- :bug:`cfp,1023` If you used links to pre-fill parts of the CfP form, you sometimes couldn’t get part the first page. +- :bug:`cfp,1023` If you used links to fill in parts of the CfP form, you sometimes could not get part the first page. - :bug:`schedule` The display of large talk images was off, extending them too far to the right. - :feature:`cfp` The availability widget now shows day names in your locale instead of always using English. - :feature:`orga:email` To prevent emails getting recorded as spam, the custom sender address is now only used when you are using a custom email server. You can still set the reply-to address. @@ -318,7 +318,7 @@ Release Notes - :feature:`admin` You can now use the ``--silent`` flag with the ``regenerate_css`` command to reduce build verbosity. - :feature:`orga:schedule,735` You can now filter talks by track and type in the schedule editor. - :feature:`orga:schedule` Room availabilities are now more fine-grained, you can set them on a 15-minute basis instead of 30-minutes as before. -- :bug:`orga` The statistics page didn’t work for events with just a single submission type. +- :bug:`orga` The statistics page did not work for events with just a single submission type. - :release:`2.1.1 <2021-01-16>` - :release:`2.1.0 <2021-01-16>` - :bug:`admin,1046` pretalx shipped an incorrect override settings file that broke email sending. @@ -331,7 +331,7 @@ Release Notes - :feature:`orga:review` Reviewers can now be asked to rate a proposal in several categories, with a total score calculated automatically. - :announcement:`schedule` Pretalx has a new schedule, with a new widget. The old widget is deprecated and will be removed in the next release. Please migrate all of your widgets to the new widget code. You can generate it in your event’s settings. - :announcement:`admin` Remember to check your access logs before upgrading to v2.1 to warn users about failing widgets. -- :feature:`api` There are two new API endpoints, ``/questions/`` and ``/answers/``, that incientally are our first writable API endpoints. The API docs have been updated. +- :feature:`api` There are two new API endpoints, ``/questions/`` and ``/answers/``, that incidentally are our first writable API endpoints. The API docs have been updated. - :feature:`admin` Email error reporting (sent to instance administrators) now includes a short explanation and a link to the pretalx issue tracker. - :feature:`api` If a speaker has selected to show their gravatar, it is now also exposed in the API in the avatar field. - :feature:`orga:email` When you send out reminders about unanswered questions, you can now target specific questions, or tracks, or submission types. @@ -372,7 +372,7 @@ Release Notes - :bug:`admin` Under specific circumstances, the ``django_sessions`` table could bloat a lot. This is fixed with the next release and the table will shrink over time as long as you regularly run the ``clearsessions`` command. - :feature:`orga:speaker,855` The filtered list of speakers in the organiser area now contains only people with confirmed *or accepted* talks, and is also better at showing the filter currently applied. - :feature:`orga:review` Organisers can now anonymise submission content for reviewers, if they choose to do anonymised reviews. They can redact or edit any part of the submission for the reviewers’ view of it to remove identifying information. -- :bug:`cfp` It wasn’t possible to hide a submission type unless accessed with an access token. (Or, well, it was possible, but the possibility was hidden.) +- :bug:`cfp` It was not possible to hide a submission type unless accessed with an access token. (Or, well, it was possible, but the possibility was hidden.) - :feature:`orga,880` The submission statistics now ignore deleted submissions. - :announcement:`admin` This version of pretalx has higher database version requirements. We now support PostgreSQL 9.6+, MariaDB 10.1+, MySQL 5.6+, and SQLite 3.8.3+. - :bug:`cfp,877` The frontend markdown preview would not render all line breaks as line breaks (only two line breaks in a row), but the server rendered version did. @@ -412,7 +412,7 @@ Release Notes - :feature:`orga:review,638` You can now determine if the answers to talk questions should be visible to reviewers. This allows you to ask personal questions of your submitters, even when you are running an anonymous review process. - :feature:`orga,648` pretalx now comes with a CfP editor that allows you to change the headline, text, and help texts on each of the CfP step pages. - :feature:`api,760` Speakers can now see and reset their API token in their profile page. -- :announcement:`dev` We have added a couple of pages to the pretalx wiki on GitHub, most importantly a list of events using pretalx, and a list of available plugins. The wiki is world-writable, so please add to it if you have an event or plugin that hasn’t been mentioned yet! +- :announcement:`dev` We have added a couple of pages to the pretalx wiki on GitHub, most importantly a list of events using pretalx, and a list of available plugins. The wiki is world-writable, so please add to it if you have an event or plugin that has not been mentioned yet! - :feature:`orga:schedule,277` The static HTML export will now be triggered when talk or speaker data is changed (as long as it’s also generated on schedule release). To protect against high server load, it will still run at most once every hour. - :feature:`schedule` To reduce scroll wheel abrasions, pretalx schedules are now tabbed with one tab per event day. - :feature:`schedule,242` pretalx has learned what breaks are. Organisers can create those in the schedule editor, and they will be shown in an appropriately muted way in the schedule. @@ -424,7 +424,7 @@ Release Notes - :release:`1.0.4 <2019-10-15>` - :bug:`schedule` In feedback pages for talks that contained multiple speakers, the email addresses of those speakers were shown next to their names. - :feature:`orga` Allow users to add an imprint URL that will be shown at the bottom of every public event page. -- :bug:`schedule` On the sneak peek preview page, markdown wasn’t rendered correctly to HTML. +- :bug:`schedule` On the sneak peek preview page, markdown was not rendered correctly to HTML. - :feature:`dev` If pretalx is running in development mode, its favicon will be red. - :feature:`dev` Plugin authors will now have access to all configuration sections starting with ``[plugin:*]``, to ease the integration of system level settings. - :feature:`api,787` Provide the file uploads a speaker added to their submission via the ``/talks`` and ``/submissions`` API endpoint. @@ -505,7 +505,7 @@ Release Notes - :bug:`orga:schedule` Changing the order of rooms made the schedule break. - :feature:`orga:review,433` Organisers can now view all reviews, except for their own submissions. - :feature:`orga,589` Before setting a new custom domain for an event, pretalx now checks if the domain has any DNS records. -- :bug:`cfp` A dependency of ours introduced an XSS vulnerability, which organisers could use to execute JavaScript during the CfP workflow of speakers via question texts. We have added a fix against this behaviour, and submitted a report including a patch to the upstream library. To prevent issues like this one in the future, we’ve moved all remaining JavaScript sources to files, and set the according CSP header, so that execution of inline JavaScript will be disabled. +- :bug:`cfp` A dependency of ours introduced an XSS vulnerability, which organisers could use to execute JavaScript during the CfP workflow of speakers via question texts. We have added a fix against this behaviour, and submitted a report including a patch to the upstream library. To prevent issues like this one in the future, we have moved all remaining JavaScript sources to files, and set the according CSP header, so that execution of inline JavaScript will be disabled. - :feature:`cfp,364` Speakers can now invite a co-speaker while in the submission process. - :feature:`schedule,62` Exporters can now opt in to show a QR code to their location. The XML and iCal exporters show a QR code linking their location by default. - :feature:`orga:schedule,477` If you only noticed after releasing your schedule that you wanted to changes something in your speaker notifications, you can now generate those emails again from the schedule editor actions menu. @@ -532,10 +532,10 @@ Release Notes - :bug:`schedule` The visual representation of a speaker’s avatar is now consistent across all image-sizes and bio-texts. - :bug:`cfp,583` When signing up with an email address with upper case letters included, pretalx only allowed to log in with a lower-cased email address. - :bug:`orga:speaker,572` People who had only deleted submissions in an event were still shown in the submitter list, which was unexpected and was since fixed. -- :feature:`lang` If only one conference language is available, pretalx doesn’t as speakers to choose it from a drop-down, as this behaviour is rather silly. -- :announcement:`admin` pretalx doesn’t run ``regenerate_css`` on startup automatically any longer. This reduces startup times. If for any reason an event does not look as it should, you can fix it by running ``python -m pretalx regenerate_css``. You will also need to execute this command on updates from now on. +- :feature:`lang` If only one conference language is available, pretalx does not as speakers to choose it from a drop-down, as this behaviour is rather silly. +- :announcement:`admin` pretalx does not run ``regenerate_css`` on startup automatically any longer. This reduces startup times. If for any reason an event does not look as it should, you can fix it by running ``python -m pretalx regenerate_css``. You will also need to execute this command on updates from now on. - :feature:`orga:schedule` You can now decide if you want to notify speakers about their changed talks when releasing a new schedule. -- :announcement:`admin` To help make other pretalx installations more secure, we’ve updated our proposed nginx configuration to include an attachment header for all files under /media, to prevent user uploaded data to be delivered directly to other users. If you host a pretalx instance, please make use of this option. +- :announcement:`admin` To help make other pretalx installations more secure, we have updated our proposed nginx configuration to include an attachment header for all files under /media, to prevent user uploaded data to be delivered directly to other users. If you host a pretalx instance, please make use of this option. - :feature:`orga` Since SVG files are nearly impossible to sanitise, pretalx has given up trying, and will no longer accept SVG files as image uploads. - :bug:`schedule` The iCal export for speakers who had both scheduled and not-yet-scheduled talks was broken. - :feature:`orga:speaker,559` Organisers can download a list of speakers as a CSV file. @@ -549,7 +549,7 @@ Release Notes - :feature:`-` You can now set the default pretalx system wide time zone and locale (defaulting to ``UTC`` and English). - :bug:`544` Organisers could see the titles of speaker information notes of all events, not just the currently active one (they could not see the details or edit them). - :feature:`504` The schedule page is now better printable. -- :bug:`-` A `bug ` in celery could make running pretalx with asynchronous workers impossible. We’ve pinned an earlier celery version that doesn’t show this problem. +- :bug:`-` A `bug ` in celery could make running pretalx with asynchronous workers impossible. We have pinned an earlier celery version that does not show this problem. - :announcement:`-` A new pretalx plugin adds media.ccc.de as a recording provider – this plugin replaces the previously inbuilt capacity of pretalx to provide recording iframes. (This functionality was never directly exposed and only accessible via the pretalx shell. It is now deprecated and will be removed in a later version.) - :feature:`-` Plugins can now provide recording iframes (via the new ``register_recording_provider`` signal and other helpers). - :feature:`-` The new ``nav_event_settings`` plugin signal allows plugins to integrate their own settings pages next to the pretalx core pages. @@ -569,7 +569,7 @@ Release Notes - :feature:`-` The ``rebuild`` command now comes with a lot more build output for ease of debugging. You can disable the build output with the new ``--silent/-s`` flag. - :feature:`476` Administrators can now delete both events and organisers. - :feature:`493` Speaker email addresses are now available via the API for users with access permissions. -- :bug:`515` Under rare circumstances, the pretalx database could reach a state pretalx couldn’t cope with due to duplicate schedule versions. +- :bug:`515` Under rare circumstances, the pretalx database could reach a state pretalx could not cope with due to duplicate schedule versions. - :feature:`512` You can now configure if speakers should provide their availability during talk submission. - :announcement:`admin` Due to an updated Django version, pretalx has dropped support for PostgreSQL 9.3 and MySQL 5.5. - :release:`0.8.0 <2018-09-23>` @@ -597,7 +597,7 @@ Release Notes - :feature:`214` The schedule editor shows warnings on scheduling conflicts, including live feedback on where you can schedule a talk. - :feature:`474` The review dashboard now features the same search and filter options as the submission list. - :bug:`473` Following the revamp of team permissions, override votes were missing from the settings. We re-introduced the settings, and improved the general handling of override votes. -- :announcement:`admin` pretalx now doesn’t support usernames any longer – as all users had to have email addresses already, you will now have to provide an email address to log in. This may confuse users – as an administrator, you can look up users’ email addresses if they don’t remember them, or change them, if necessary. +- :announcement:`admin` pretalx now does not support usernames any longer – as all users had to have email addresses already, you will now have to provide an email address to log in. This may confuse users – as an administrator, you can look up users’ email addresses if they don’t remember them, or change them, if necessary. - :bug:`-` You could make questions inactive, but not delete them. - :feature:`408` You can now add length restrictions to abstracts, descriptions, speaker biographies, and all text-based questions. - :feature:`-` When linking to a talk on social media, those pages will show the talk image. @@ -677,7 +677,7 @@ Release Notes - :bug:`-` The organiser view now always uses the event timezone. - :release:`0.4.1 <2018-02-05>` - :bug:`335` CfP was not editable due to missing "Save" button. -- :bug:`336` Organisers couldn’t add new questions. +- :bug:`336` Organisers could not add new questions. - :release:`0.4.0 <2018-02-04>` - :feature:`-` A page in the organiser area lists and links all possible data exports in one export page. - :feature:`322` You may now import XML files to release a new schedule. diff --git a/doc/developer/plugins/customview.rst b/doc/developer/plugins/customview.rst index 380aeabeb..5617e0fa8 100644 --- a/doc/developer/plugins/customview.rst +++ b/doc/developer/plugins/customview.rst @@ -42,8 +42,9 @@ If you want to add a custom view to the organiser area of an event, register an ), ] -If you just created your `urls.py` file and you already had the dev-server -running, you’ll now have to restart it for the new file to be recognized. +If you just created your `urls.py` file and you already had the development +server running, you’ll now have to restart it for the new file to be +recognised. If your view is event-specific, you have to name one parameter in your URL ``event``. By convention, all plugin URLs except for backend URLs start with @@ -96,7 +97,7 @@ Frontend views Frontend views work pretty much like organiser area views. Take care that your URL starts with ``fr"^(?P[{SLUG_REGEX}]+)/p/mypluginname"`` for event -related urls or ``f"^p/mypluginname"`` for global views. You can then write a +related URLs or ``f"^p/mypluginname"`` for global views. You can then write a regular view. It will be automatically ensured that: * The requested event exists diff --git a/doc/developer/plugins/exporter.rst b/doc/developer/plugins/exporter.rst index 37475a1b5..1c220ca90 100644 --- a/doc/developer/plugins/exporter.rst +++ b/doc/developer/plugins/exporter.rst @@ -76,5 +76,6 @@ considerations, since the mixin takes care of all that. Access ------ -The export will now be available for organisers in the schedule related export view. -If you’ve set ``public = True``, it will also show up in the drop down in the event agenda. +The export will now be available for organisers in the schedule related export +view. If you have set ``public = True``, it will also show up in the drop down +in the event agenda. diff --git a/doc/developer/plugins/plugins.rst b/doc/developer/plugins/plugins.rst index 81d43125f..d6e84beec 100644 --- a/doc/developer/plugins/plugins.rst +++ b/doc/developer/plugins/plugins.rst @@ -23,13 +23,13 @@ time, we created a `cookiecutter`_ template that you can use like this:: (env)$ cookiecutter https://github.com/pretalx/pretalx-plugin-cookiecutter This will ask you some questions and then create a project folder for your plugin. -Afterwards install your plugin into pretalx: +Afterwards install your plugin into pretalx:: (env)$ cd pretalx-pluginname (env)$ python -m pip install -e . -If you already had it running, you’ll now have to restart your pretalx dev-server -for it to recognize the new plugin. +If you already had it running, you’ll now have to restart your pretalx +development server process for it to recognise the new plugin. About this Documentation ------------------------ @@ -145,11 +145,11 @@ your Django application label. Configuration ------------- -Occasionally, your plugin may need system-level configuration that doesn’t need -its own API. In this case, you can ask users to provide this configuration via -their ``pretalx.cfg`` file. Ask them to put their configuration in a section -with the title ``[plugin:your_plugin_name]``, which pretalx will then provide -in ``settings.PLUGIN_SETTINGS[your_plugin_name]``, like this:: +Occasionally, your plugin may need system-level configuration that does not +need its own API. In this case, you can ask users to provide this configuration +via their ``pretalx.cfg`` file. Ask them to put their configuration in a +section with the title ``[plugin:your_plugin_name]``, which pretalx will then +provide in ``settings.PLUGIN_SETTINGS[your_plugin_name]``, like this:: [plugin:pretalx_soap] endpoint=https://example.com diff --git a/doc/developer/setup.rst b/doc/developer/setup.rst index 72deafddd..d711cd14f 100644 --- a/doc/developer/setup.rst +++ b/doc/developer/setup.rst @@ -121,7 +121,7 @@ compile the JavaScript files:: (env)$ python manage.py rebuild --npm-install If you want to change the JavaScript code, you can run the following command, which combines -the Python and the JavaScript dev servers:: +the Python and the JavaScript development servers:: (env)$ python manage.py devserver diff --git a/doc/developer/translating.rst b/doc/developer/translating.rst index 265483983..c4166dfd2 100644 --- a/doc/developer/translating.rst +++ b/doc/developer/translating.rst @@ -67,7 +67,7 @@ to help with unofficial languages). Updating translations --------------------- -New or changed translations aren’t visible in pretalx immediately. We update +New or changed translations are not visible in pretalx immediately. We update the translations in the development version of pretalx regularly (roughly weekly), and also before every new release. diff --git a/doc/maintainer/release.rst b/doc/maintainer/release.rst index ac0e70fc5..240549093 100644 --- a/doc/maintainer/release.rst +++ b/doc/maintainer/release.rst @@ -11,7 +11,7 @@ Boarding checks 3. Are there pending translations from `Weblate `_? Merge them. 4. Are all locales with more than 75% coverage included in the release? 5. Update the translation percentages from `translate.pretalx.com `_. -6. If new translations were added, add new fullcalendar locales (you have to download the `release archive `_) and extract the locales from there), and make sure that flags (in input fields) for the new locale are shown. +6. If new translations were added, add new calendar locales (you have to download the `release archive `_) and extract the locales from there), and make sure that flags (in input fields) for the new locale are shown. 7. Are there warnings about missing migrations? 8. Any blockers to see `in our issues `_? 9. Are there any TODOs that you have to resolve? @@ -42,9 +42,9 @@ Take-off and landing 8. Publish the blog post. 9. Add the release on `GitHub `_ (upload the archive you uploaded to PyPI, and add a link to the correct section of the :ref:`changelog`) 10. Upgrade `the docker repository `_ to the current commit **and tag the commit as vx.y.z**. -11. Increment version number to version+1.dev0 in ``src/pretalx/__init__.py``. +11. Increment version number to ``version+1.dev0`` in ``src/pretalx/__init__.py``. 12. ``rm -rf pretalx-release && deactivate && rmvirtualenv pretalx-release`` 13. Update version numbers in update checker (``versions.py``) and deploy. 14. Update any plugins waiting for the new release. 15. Check if the docker image build was successful. -16. Post about the release on Fedi, Twitter and LinkedIn. +16. Post about the release on ``chaos.social``, Twitter and LinkedIn. diff --git a/doc/spelling_wordlist.txt b/doc/spelling_wordlist.txt index cbe872848..9fef337cd 100644 --- a/doc/spelling_wordlist.txt +++ b/doc/spelling_wordlist.txt @@ -1,16 +1,20 @@ anonymisation anonymise anonymised +anonymising Ansible +auth availabilities backend backends BaseExporter +BaseMailTextPlaceholder Bcc boolean booleans browsable canceled +categorys ccc CfP changelog @@ -20,6 +24,7 @@ cronjob cryptographic CSP csp +ctrl datetime datetimes de @@ -27,6 +32,7 @@ Debian discoverable Django drag'n'drop +dropdown embeddable env ETags @@ -34,26 +40,33 @@ favicon filesystem frab frontend -Gmail gettext +Gmail gravatar GSuite gunicorn +href iCal iframe iframes +informations installable +integrations iterable Jorian libffi +lightbox linters localhost lookups lossy +makemessages middleware mixin namespace nginx +nodejs +noopener npm plugin plugins @@ -63,17 +76,24 @@ pretalx pytest queryset redis +ro slugified stdout +strikethrough stylesheet stylesheets subdirectory submitters submodule subpath +subtractive systemd templating transactional +txt +typeahead +uncheck +Venueless virtualenv wip Woltjer From 5e174f07baab8d55b0abc510960324ff32f9b26a Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 13:02:42 +0200 Subject: [PATCH 061/195] Fix links in docs --- doc/administrator/installation.rst | 4 ++-- doc/conf.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/administrator/installation.rst b/doc/administrator/installation.rst index 0cec29d57..b0d691c98 100644 --- a/doc/administrator/installation.rst +++ b/doc/administrator/installation.rst @@ -284,10 +284,10 @@ If you want to read about updates, backups, and monitoring, head over to our .. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/ .. _Let’s Encrypt: https://letsencrypt.org/ .. _PostgreSQL: https://www.postgresql.org/docs/ -.. _redis: https://redis.io/documentation +.. _redis: https://redis.io/docs/latest/ .. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall .. _strong encryption settings: https://mozilla.github.io/server-side-tls/ssl-config-generator/ .. _docker-compose setup: https://github.com/pretalx/pretalx-docker -.. _pretalx.com: https://pretalx.com +.. _pretalx.com: https://pretalx.com/p/about/ .. _nodejs: https://github.com/nodesource/distributions/blob/master/README.md .. _supported version of nodejs: https://nodejs.org/en/about/previous-releases diff --git a/doc/conf.py b/doc/conf.py index b5cbd8dd9..345931880 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -117,7 +117,11 @@ 'http://localhost', 'http://127.0.0.1', r'https://github.com/pretalx/pretalx/issues/\d+', # The release notes are auto generated and contain a LOT of issue links + r'https://github.com/pretalx/pretalx/issues/new', # Requires login + r'https://github.com/pretalx/pretalx/discussions/new', # Requires login "https://translate.pretalx.com/projects/pretalx/pretalx/#repository", # Only accessible by admins + "https://github.com/fullcalendar/fullcalendar/releases/download/v6.1.5/fullcalendar-6.1.5.zip", # redirects to cdn + "https://www.patreon.com/rixx", # spurious errors, sigh ] # GitHub integration From 481b65c34449a4377d32e6dd4ad4583d82858eef Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 13:03:21 +0200 Subject: [PATCH 062/195] Add Weblate as known word --- doc/spelling_wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/spelling_wordlist.txt b/doc/spelling_wordlist.txt index 9fef337cd..b379088c3 100644 --- a/doc/spelling_wordlist.txt +++ b/doc/spelling_wordlist.txt @@ -95,5 +95,6 @@ typeahead uncheck Venueless virtualenv +Weblate wip Woltjer From 5f5c8108f04593b364856c1e0b988ed0a122c237 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 13:44:03 +0200 Subject: [PATCH 063/195] Use ready-made black action --- .github/workflows/style.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index aef1fabd5..0119c0da7 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -52,15 +52,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: psf/black@stable with: - python-version-file: ".github/workflows/python-version.txt" - cache: "pip" - - name: Install Dependencies - run: python -m pip install -Ue ".[dev]" - - name: Run black - run: black --check . - working-directory: ./src + src: "./src" html: name: HTML checks runs-on: ubuntu-latest From 3aa3c5d2f8a7474a6ca6bc5523dbe5091670112c Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 14:15:14 +0200 Subject: [PATCH 064/195] Add flake8 annotations --- .github/workflows/style.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 0119c0da7..6e856f619 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -44,6 +44,8 @@ jobs: cache: "pip" - name: Install Dependencies run: python -m pip install -Ue ".[dev]" + - name: Setup flake8 annotations + uses: rbialon/flake8-annotations@v1 - name: Run flake8 run: flake8 . working-directory: ./src @@ -52,6 +54,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version-file: ".github/workflows/python-version.txt" + cache: "pip" - uses: psf/black@stable with: src: "./src" From 95309fcf9df2803bb76b68db084b215a9defe3a7 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 14:35:43 +0200 Subject: [PATCH 065/195] Annotate black errors in PRs --- .github/workflows/matchers/black.json | 15 +++++++++++++++ .github/workflows/style.yml | 2 ++ 2 files changed, 17 insertions(+) create mode 100644 .github/workflows/matchers/black.json diff --git a/.github/workflows/matchers/black.json b/.github/workflows/matchers/black.json new file mode 100644 index 000000000..605f10c27 --- /dev/null +++ b/.github/workflows/matchers/black.json @@ -0,0 +1,15 @@ +{ + "problemMatcher": [ + { + "owner": "black", + "severity": "error", + "pattern": [ + { + "regexp": "^(would reformat) (.+)$", + "file": 2, + "message": 1 + } + ] + } + ] +} diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 6e856f619..d119268b8 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -58,6 +58,8 @@ jobs: with: python-version-file: ".github/workflows/python-version.txt" cache: "pip" + - name: Add problem matcher for black + run: echo "::add-matcher::.github/workflows/matchers/black.json" - uses: psf/black@stable with: src: "./src" From f40273c8bd1bb6cd1beb5673de21efb6eeedff73 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 15:07:41 +0200 Subject: [PATCH 066/195] Remove duplicate (incorrect) email button closes #1771 --- .../orga/templates/orga/submission/base.html | 2 +- .../templates/orga/submission/content.html | 18 +++++------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/pretalx/orga/templates/orga/submission/base.html b/src/pretalx/orga/templates/orga/submission/base.html index 5b67b98df..ef977c4a6 100644 --- a/src/pretalx/orga/templates/orga/submission/base.html +++ b/src/pretalx/orga/templates/orga/submission/base.html @@ -80,7 +80,7 @@

    {% if can_send_mails %} - {% translate "Send email" %} + {% translate "Send email to speakers" %} {% endif %} {% if submission.state == "confirmed" or can_view_speakers %} diff --git a/src/pretalx/orga/templates/orga/submission/content.html b/src/pretalx/orga/templates/orga/submission/content.html index 6b31c8655..e8e1f47ad 100644 --- a/src/pretalx/orga/templates/orga/submission/content.html +++ b/src/pretalx/orga/templates/orga/submission/content.html @@ -98,22 +98,14 @@ {% bootstrap_form questions_form layout='event' %} {% endif %} - {% if can_send_mails or not form.read_only %} + {% if not form.read_only %}
    - {% if form.instance.code and can_send_mails %} - - - {% translate "Send mail to speakers" %} - - {% endif %} - {% if not form.read_only %} - - {% endif %} +
    {% endif %} From 6b37e2225d8123ab230402e4166dba3d1c6086aa Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 15:09:46 +0200 Subject: [PATCH 067/195] Fix bug in review phase access --- src/pretalx/event/models/event.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pretalx/event/models/event.py b/src/pretalx/event/models/event.py index 776bac1e0..ab3e5dc1b 100644 --- a/src/pretalx/event/models/event.py +++ b/src/pretalx/event/models/event.py @@ -954,7 +954,6 @@ def active_review_phase(self): can_see_other_reviews="after_review", can_see_speaker_names=True, ) - return self.update_review_phase() def update_review_phase(self): """This method activates the next review phase if the current one is From ef292574d37bde50ea260450b270c2d90daba30f Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 15:52:13 +0200 Subject: [PATCH 068/195] Fix review phase deletion --- src/pretalx/orga/views/event.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pretalx/orga/views/event.py b/src/pretalx/orga/views/event.py index 87537d835..5e5ba371e 100644 --- a/src/pretalx/orga/views/event.py +++ b/src/pretalx/orga/views/event.py @@ -387,7 +387,6 @@ def action_back_url(self): return self.request.event.orga_urls.review_settings def post(self, request, *args, **kwargs): - super().dispatch(request, *args, **kwargs) phase = self.get_object() phase.delete() return redirect(self.request.event.orga_urls.review_settings) From 0d5a26223506be0a060ed3c603a0cb8b02023cc7 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 17 May 2024 17:08:08 +0200 Subject: [PATCH 069/195] Use bash in CI --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4a1a465c8..a8885b1b4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -71,6 +71,7 @@ jobs: PRETALX_CONFIG_FILE: 'tests/ci_${{ matrix.database }}.cfg' - name: Show coverage as build info working-directory: ./src + shell: bash # needed to make echo work as expected run: | python -m coverage json From c9fc2c345d68454a3b117d7ad1a5101a514d5434 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Sun, 19 May 2024 13:43:31 +0200 Subject: [PATCH 070/195] Add pep8-naming to linting --- pyproject.toml | 1 + src/pretalx/common/exporter.py | 1 + src/pretalx/common/models/transaction.py | 6 +++--- src/pretalx/settings.py | 2 +- src/setup.cfg | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 174774961..f3400c085 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,6 +85,7 @@ dev = [ "isort", "jsonschema", "lxml", + "pep8-naming", "pytest", "pytest-cov", "pytest-django", diff --git a/src/pretalx/common/exporter.py b/src/pretalx/common/exporter.py index 2c20e9aa7..20a0b60fc 100644 --- a/src/pretalx/common/exporter.py +++ b/src/pretalx/common/exporter.py @@ -1,5 +1,6 @@ from io import StringIO from urllib.parse import quote +from xml.etree import ElementTree import qrcode import qrcode.image.svg diff --git a/src/pretalx/common/models/transaction.py b/src/pretalx/common/models/transaction.py index 291cb9842..70a59109b 100644 --- a/src/pretalx/common/models/transaction.py +++ b/src/pretalx/common/models/transaction.py @@ -16,14 +16,14 @@ def rolledback_transaction(): frequently. """ - class DummyRollbackException(Exception): + class DummyRollbackError(Exception): pass try: with transaction.atomic(): yield - raise DummyRollbackException() - except DummyRollbackException: + raise DummyRollbackError() + except DummyRollbackError: pass else: # pragma: no cover raise Exception("Invalid state, should have rolled back.") diff --git a/src/pretalx/settings.py b/src/pretalx/settings.py index 59f314217..875bcfa85 100644 --- a/src/pretalx/settings.py +++ b/src/pretalx/settings.py @@ -689,7 +689,7 @@ def merge_csp(*options, config=None): config_files=CONFIG_FILES, db_name=db_name, db_backend=db_backend, - LOG_DIR=LOG_DIR, + log_dir=LOG_DIR, plugins=PLUGINS, ) diff --git a/src/setup.cfg b/src/setup.cfg index 6f3eb2c62..c306d75fa 100644 --- a/src/setup.cfg +++ b/src/setup.cfg @@ -48,3 +48,4 @@ skip = migrations,settings.py,wsgi.py,celery_app.py,test_settings.py,local ignore = E203, E231, E266, E501, W503, W605, B028 max-line-length = 160 exclude = migrations,static,_static,build,*settings.py,.tox/*,local +ignore-names = allDay,get_allDay,urls,orga_urls,api_urls,SendMailException,extendMarkdown,setUp,setUpTestData From 89241eaebba903fe1f7d7b8612aac0379fda476a Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Sun, 19 May 2024 13:48:59 +0200 Subject: [PATCH 071/195] Suppress Markdown DEBUG messages --- src/pretalx/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pretalx/settings.py b/src/pretalx/settings.py index 875bcfa85..7c98a1242 100644 --- a/src/pretalx/settings.py +++ b/src/pretalx/settings.py @@ -1,3 +1,4 @@ +import logging import os import sys from contextlib import suppress @@ -10,8 +11,8 @@ from pkg_resources import iter_entry_points from pretalx import __version__ -from pretalx.common.text.console import log_initial from pretalx.common.settings.config import build_config +from pretalx.common.text.console import log_initial config, CONFIG_FILES = build_config() CONFIG = config @@ -265,6 +266,7 @@ def merge_csp(*options, config=None): }, }, } +logging.getLogger("MARKDOWN").setLevel(logging.WARNING) email_level = config.get("logging", "email_level", fallback="ERROR") or "ERROR" emails = config.get("logging", "email", fallback="").split(",") From 66d51e23ee3efeb1e42b78e8a41c564a64e29f24 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Sun, 19 May 2024 13:56:56 +0200 Subject: [PATCH 072/195] Fix locale not updating closes #1773 --- src/pretalx/common/forms/forms.py | 2 +- src/pretalx/common/forms/widgets.py | 2 +- src/pretalx/orga/forms/submission.py | 2 +- src/pretalx/orga/phrases.py | 2 +- src/pretalx/orga/views/cards.py | 2 +- src/pretalx/orga/views/person.py | 2 +- src/pretalx/orga/views/submission.py | 2 +- src/pretalx/submission/forms/feedback.py | 2 +- src/tests/screenshots/conftest.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pretalx/common/forms/forms.py b/src/pretalx/common/forms/forms.py index 32756abdf..773123ce4 100644 --- a/src/pretalx/common/forms/forms.py +++ b/src/pretalx/common/forms/forms.py @@ -1,6 +1,6 @@ import i18nfield.forms from django import forms -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ class SearchForm(forms.Form): diff --git a/src/pretalx/common/forms/widgets.py b/src/pretalx/common/forms/widgets.py index 98d05e873..a79aba285 100644 --- a/src/pretalx/common/forms/widgets.py +++ b/src/pretalx/common/forms/widgets.py @@ -3,7 +3,7 @@ from django.core.files import File from django.forms import ClearableFileInput, PasswordInput, Textarea from django.utils.safestring import mark_safe -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ class PasswordStrengthInput(PasswordInput): diff --git a/src/pretalx/orga/forms/submission.py b/src/pretalx/orga/forms/submission.py index 5ab17ec1a..960de4a75 100644 --- a/src/pretalx/orga/forms/submission.py +++ b/src/pretalx/orga/forms/submission.py @@ -2,7 +2,7 @@ from django import forms from django.utils.formats import get_format -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField from pretalx.common.forms.fields import ImageField diff --git a/src/pretalx/orga/phrases.py b/src/pretalx/orga/phrases.py index 5e675d976..ad8bf18d1 100644 --- a/src/pretalx/orga/phrases.py +++ b/src/pretalx/orga/phrases.py @@ -1,4 +1,4 @@ -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ from pretalx.common.text.phrases import Phrases diff --git a/src/pretalx/orga/views/cards.py b/src/pretalx/orga/views/cards.py index ccc1dc0be..23c917336 100644 --- a/src/pretalx/orga/views/cards.py +++ b/src/pretalx/orga/views/cards.py @@ -8,7 +8,7 @@ from django.shortcuts import redirect from django.utils.html import conditional_escape from django.utils.timezone import now -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ from django.views.generic import View from reportlab.graphics import renderPDF from reportlab.graphics.barcode import qr diff --git a/src/pretalx/orga/views/person.py b/src/pretalx/orga/views/person.py index 895bd9a67..705ea49ff 100644 --- a/src/pretalx/orga/views/person.py +++ b/src/pretalx/orga/views/person.py @@ -6,7 +6,7 @@ from django.shortcuts import redirect from django.urls import reverse from django.utils.http import url_has_allowed_host_and_scheme -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ from django.views.generic import View from django_scopes import scopes_disabled diff --git a/src/pretalx/orga/views/submission.py b/src/pretalx/orga/views/submission.py index faf6b8752..8daf6c28a 100644 --- a/src/pretalx/orga/views/submission.py +++ b/src/pretalx/orga/views/submission.py @@ -18,7 +18,7 @@ from django.utils.functional import cached_property from django.utils.http import url_has_allowed_host_and_scheme from django.utils.timezone import now -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ from django.utils.translation import override from django.views.generic import FormView, ListView, TemplateView, UpdateView, View from django_context_decorator import context diff --git a/src/pretalx/submission/forms/feedback.py b/src/pretalx/submission/forms/feedback.py index 9cb218819..140ccd823 100644 --- a/src/pretalx/submission/forms/feedback.py +++ b/src/pretalx/submission/forms/feedback.py @@ -1,5 +1,5 @@ from django import forms -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ from pretalx.common.forms.mixins import ReadOnlyFlag from pretalx.common.forms.widgets import MarkdownWidget diff --git a/src/tests/screenshots/conftest.py b/src/tests/screenshots/conftest.py index 41e147b97..4cb051b34 100644 --- a/src/tests/screenshots/conftest.py +++ b/src/tests/screenshots/conftest.py @@ -4,7 +4,7 @@ import pytest from django.core.management import call_command from django.utils.timezone import now -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ from django_scopes import scope SEED = random.randint(0, 100000) From c753fccb6b1fa9aa9353bcbd4b624daf8ec6b4e8 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Thu, 15 Aug 2024 16:28:12 +0200 Subject: [PATCH 073/195] Split up CfP field config table --- src/pretalx/orga/templates/orga/cfp/text.html | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/pretalx/orga/templates/orga/cfp/text.html b/src/pretalx/orga/templates/orga/cfp/text.html index 0bc4dec30..2c60b2df0 100644 --- a/src/pretalx/orga/templates/orga/cfp/text.html +++ b/src/pretalx/orga/templates/orga/cfp/text.html @@ -117,12 +117,6 @@ - - {% translate "Availability" %} - {% bootstrap_field sform.cfp_ask_availabilities layout='event-inline' %} - - - {% translate "Notes" %} {% bootstrap_field sform.cfp_ask_notes layout='event-inline' %} @@ -149,6 +143,39 @@

    + +
    + {% translate "Speaker information" %} +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {% translate "Minimum length" %}{% translate "Maximum length" %}
    {% translate "Biography" %}{% bootstrap_field sform.cfp_ask_biography layout='event-inline' %}{% bootstrap_field sform.cfp_biography_min_length use_label=False layout='inline' %}{% bootstrap_field sform.cfp_biography_max_length use_label=False layout='inline' %}
    {% translate "Profile picture" %}{% bootstrap_field sform.cfp_ask_avatar layout='event-inline' %}
    {% translate "Availability" %}{% bootstrap_field sform.cfp_ask_availabilities layout='event-inline' %}
    {% bootstrap_field form.count_length_in layout='event' %}
    From 518dbfc6211b5650a56a1f793aaca4a7f16c2be0 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Thu, 15 Aug 2024 21:46:41 +0200 Subject: [PATCH 074/195] Implement favourite saving backend --- src/pretalx/agenda/views/schedule.py | 4 +- src/pretalx/api/serializers/submission.py | 5 ++ src/pretalx/api/urls.py | 2 +- src/pretalx/api/views/submission.py | 50 +++++++++++++---- src/pretalx/person/forms.py | 2 +- src/pretalx/schedule/models/schedule.py | 4 +- ...favourite_submissionfavouritedeprecated.py | 19 +++++++ .../migrations/0078_submissionfavourite.py | 54 +++++++++++++++++++ src/pretalx/submission/models/submission.py | 19 +++++-- src/tests/api/test_api_submissions.py | 2 + 10 files changed, 142 insertions(+), 19 deletions(-) create mode 100644 src/pretalx/submission/migrations/0077_rename_submissionfavourite_submissionfavouritedeprecated.py create mode 100644 src/pretalx/submission/migrations/0078_submissionfavourite.py diff --git a/src/pretalx/agenda/views/schedule.py b/src/pretalx/agenda/views/schedule.py index f5297aff0..ea9f92753 100644 --- a/src/pretalx/agenda/views/schedule.py +++ b/src/pretalx/agenda/views/schedule.py @@ -25,7 +25,7 @@ from pretalx.common.views.mixins import EventPermissionRequired from pretalx.schedule.ascii import draw_ascii_schedule from pretalx.schedule.exporters import ScheduleData -from pretalx.submission.models.submission import SubmissionFavourite +from pretalx.submission.models.submission import SubmissionFavouriteDeprecated logger = logging.getLogger(__name__) @@ -119,7 +119,7 @@ def get(self, request, *args, **kwargs): exporter.talk_ids = request.GET.get("talks").split(",") else: return HttpResponseRedirect(self.request.event.urls.login) - favs_talks = SubmissionFavourite.objects.filter(user=self.request.user.id) + favs_talks = SubmissionFavouriteDeprecated.objects.filter(user=self.request.user.id) if favs_talks.exists(): exporter.talk_ids = favs_talks[0].talk_list diff --git a/src/pretalx/api/serializers/submission.py b/src/pretalx/api/serializers/submission.py index 810b79e5b..0eabe25d0 100644 --- a/src/pretalx/api/serializers/submission.py +++ b/src/pretalx/api/serializers/submission.py @@ -146,6 +146,7 @@ class SubmissionOrgaSerializer(SubmissionSerializer): tags = SerializerMethodField() tag_ids = SerializerMethodField() created = SerializerMethodField() + favourite_count = SerializerMethodField() speaker_serializer_class = SubmitterOrgaSerializer @@ -161,6 +162,9 @@ def get_tags(self, obj): def get_tag_ids(self, obj): return list(obj.tags.all().values_list("id", flat=True)) + def get_favourite_count(self, obj): + return obj.favourite_count + class Meta(SubmissionSerializer.Meta): fields = SubmissionSerializer.Meta.fields + [ "created", @@ -170,6 +174,7 @@ class Meta(SubmissionSerializer.Meta): "internal_notes", "tags", "tag_ids", + "favourite_count", ] diff --git a/src/pretalx/api/urls.py b/src/pretalx/api/urls.py index b94ecd78b..5caec3ba3 100644 --- a/src/pretalx/api/urls.py +++ b/src/pretalx/api/urls.py @@ -27,6 +27,6 @@ path("auth/", obtain_auth_token), path("events//", include(event_router.urls)), path( - "events//favourite-talk/", submission.SubmissionFavouriteView.as_view() + "events//favourite-talk/", submission.SubmissionFavouriteDeprecatedView.as_view() ), ] diff --git a/src/pretalx/api/views/submission.py b/src/pretalx/api/views/submission.py index 30ac98e5f..483109348 100644 --- a/src/pretalx/api/views/submission.py +++ b/src/pretalx/api/views/submission.py @@ -8,6 +8,8 @@ from django.http import Http404, JsonResponse from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator +from django.db.models import Count +from django.http import Http404 from django.utils.functional import cached_property from django.views import View from django.views.decorators.csrf import csrf_exempt @@ -15,6 +17,9 @@ from django_scopes import scopes_disabled from rest_framework import status, viewsets from rest_framework.authentication import get_authorization_header +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.response import Response from pretalx.api.serializers.submission import ( ScheduleListSerializer, @@ -29,8 +34,8 @@ from pretalx.schedule.models import Schedule from pretalx.submission.models import Submission, SubmissionStates, Tag from pretalx.submission.models.submission import ( - SubmissionFavourite, - SubmissionFavouriteSerializer, + SubmissionFavouriteDeprecated, + SubmissionFavouriteDeprecatedSerializer, ) with scopes_disabled(): @@ -54,6 +59,9 @@ class SubmissionViewSet(viewsets.ReadOnlyModelViewSet): filterset_class = SubmissionFilter def get_queryset(self): + base_qs = self.request.event.submissions.all().annotate( + favourite_count=Count("favourites") + ) if self.request._request.path.endswith( "/talks/" ) or not self.request.user.has_perm( @@ -66,12 +74,12 @@ def get_queryset(self): or not self.request.event.current_schedule ): return Submission.objects.none() - return self.request.event.submissions.filter( + return base_qs.filter( pk__in=self.request.event.current_schedule.talks.filter( is_visible=True ).values_list("submission_id", flat=True) ) - return self.request.event.submissions.all() + return base_qs def get_serializer_class(self): if self.request.user.has_perm("orga.change_submissions", self.request.event): @@ -98,6 +106,30 @@ def get_serializer(self, *args, **kwargs): **kwargs, ) + @action(detail=False, methods=["GET"]) + def favourites(self, request): + if not request.user.is_authenticated: + raise Http404 + return Response( + [ + sub.code + for sub in Submission.objects.filter( + favourites__in=[request.user], event=request.event + ) + ] + ) + + @action(detail=True, methods=["POST", "DELETE"]) + def favourite(self, request, **kwargs): + if not request.user.is_authenticated: + raise Http404 + submission = self.get_object() + if request.method == "POST": + submission.save_favourite(request.user) + elif request.method == "DELETE": + submission.remove_favourite(request.user) + return Response({}) + class ScheduleViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = ScheduleSerializer @@ -151,7 +183,7 @@ def get_queryset(self): return Tag.objects.none() -class SubmissionFavouriteView(View): +class SubmissionFavouriteDeprecatedView(View): """ A view for handling user's favourite talks. @@ -170,7 +202,7 @@ def get(self, request, *args, **kwargs) -> JsonResponse: try: user_id = request.user.id # user_id = 52 - fav_talks = get_object_or_404(SubmissionFavourite, user=user_id) + fav_talks = get_object_or_404(SubmissionFavouriteDeprecated, user=user_id) return JsonResponse(fav_talks.talk_list, safe=False) except Http404: # As user not have any favourite talk yet @@ -191,7 +223,7 @@ def post(request, *args, **kwargs) -> JsonResponse: try: user_id = request.user.id if user_id is None: - user_id = SubmissionFavouriteView.get_user_from_token( + user_id = SubmissionFavouriteDeprecatedView.get_user_from_token( request, request.event.venueless_settings ) talk_list = json.loads(request.body.decode()) @@ -202,11 +234,11 @@ def post(request, *args, **kwargs) -> JsonResponse: talk_list_valid.append(talk) data = {"user": user_id, "talk_list": talk_list_valid} - serializer = SubmissionFavouriteSerializer(data=data) + serializer = SubmissionFavouriteDeprecatedSerializer(data=data) if serializer.is_valid(): fav_talks = serializer.save(user_id, talk_list_valid) # call to video for update favourite talks - token = SubmissionFavouriteView.get_user_video_token( + token = SubmissionFavouriteDeprecatedView.get_user_video_token( request.user.code, request.event.venueless_settings ) video_url = request.event.venueless_settings.url + "favourite-talk/" diff --git a/src/pretalx/person/forms.py b/src/pretalx/person/forms.py index e1cf30166..3dc2df404 100644 --- a/src/pretalx/person/forms.py +++ b/src/pretalx/person/forms.py @@ -12,7 +12,7 @@ from pretalx.common.forms.fields import ( PasswordConfirmationField, PasswordField, - SizeFileField, + SizeFileField, ImageField, ) from pretalx.common.forms.mixins import ( I18nHelpText, diff --git a/src/pretalx/schedule/models/schedule.py b/src/pretalx/schedule/models/schedule.py index f64951b4f..0195f2f43 100644 --- a/src/pretalx/schedule/models/schedule.py +++ b/src/pretalx/schedule/models/schedule.py @@ -21,7 +21,7 @@ from pretalx.schedule.notifications import render_notifications from pretalx.schedule.signals import schedule_release from pretalx.submission.models import SubmissionStates -from pretalx.submission.models.submission import SubmissionFavourite +from pretalx.submission.models.submission import SubmissionFavouriteDeprecated class Schedule(PretalxModel): @@ -733,7 +733,7 @@ def __str__(self) -> str: def count_fav_talk(submission_code): # Cast talk_list to TextField for using the contains lookup count = ( - SubmissionFavourite.objects.annotate( + SubmissionFavouriteDeprecated.objects.annotate( talk_list_str=Cast("talk_list", TextField()) ) .filter(talk_list_str__contains=str(submission_code)) diff --git a/src/pretalx/submission/migrations/0077_rename_submissionfavourite_submissionfavouritedeprecated.py b/src/pretalx/submission/migrations/0077_rename_submissionfavourite_submissionfavouritedeprecated.py new file mode 100644 index 000000000..9c80abc57 --- /dev/null +++ b/src/pretalx/submission/migrations/0077_rename_submissionfavourite_submissionfavouritedeprecated.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.1 on 2024-10-03 09:57 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("submission", "0076_submissionfavourite"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.RenameModel( + old_name="SubmissionFavourite", + new_name="SubmissionFavouriteDeprecated", + ), + ] diff --git a/src/pretalx/submission/migrations/0078_submissionfavourite.py b/src/pretalx/submission/migrations/0078_submissionfavourite.py new file mode 100644 index 000000000..404c3a5ec --- /dev/null +++ b/src/pretalx/submission/migrations/0078_submissionfavourite.py @@ -0,0 +1,54 @@ +# Generated by Django 5.1.1 on 2024-10-03 09:57 + +import django.db.models.deletion +import pretalx.common.models.mixins +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("submission", "0077_rename_submissionfavourite_submissionfavouritedeprecated"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="SubmissionFavourite", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, primary_key=True, serialize=False + ), + ), + ("created", models.DateTimeField(auto_now_add=True, null=True)), + ("updated", models.DateTimeField(auto_now=True, null=True)), + ( + "submission", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="favourites", + to="submission.submission", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="submission_favourites", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + bases=( + pretalx.common.models.mixins.LogMixin, + pretalx.common.models.mixins.FileCleanupMixin, + models.Model, + ), + ), + ] diff --git a/src/pretalx/submission/models/submission.py b/src/pretalx/submission/models/submission.py index bf261be0d..7ec3ff7c5 100644 --- a/src/pretalx/submission/models/submission.py +++ b/src/pretalx/submission/models/submission.py @@ -940,7 +940,7 @@ def send_invite(self, to, _from=None, subject=None, text=None): send_invite.alters_data = True -class SubmissionFavourite(models.Model): +class SubmissionFavouriteDeprecated(models.Model): user = models.OneToOneField( to="person.User", related_name="submission_favorites", @@ -953,7 +953,7 @@ class Meta: db_table = '"submission_submission_favourites"' -class SubmissionFavouriteSerializer(serializers.ModelSerializer): +class SubmissionFavouriteDeprecatedSerializer(serializers.ModelSerializer): user = serializers.SlugRelatedField(slug_field="id", read_only=True) @@ -962,15 +962,26 @@ def __init__(self, user_id=None, **kwargs): self.user = user_id class Meta: - model = SubmissionFavourite + model = SubmissionFavouriteDeprecated fields = ["user", "talk_list"] def save(self, user_id, talk_code): with scopes_disabled(): user = get_object_or_404(User, id=user_id) - submission_fav, created = SubmissionFavourite.objects.get_or_create( + submission_fav, created = SubmissionFavouriteDeprecated.objects.get_or_create( user=user ) submission_fav.talk_list = talk_code submission_fav.save() return submission_fav +class SubmissionFavourite(PretalxModel): + user = models.ForeignKey( + to="person.User", + on_delete=models.CASCADE, + related_name="submission_favourites", + ) + submission = models.ForeignKey( + to="submission.Submission", + on_delete=models.CASCADE, + related_name="favourites", + ) diff --git a/src/tests/api/test_api_submissions.py b/src/tests/api/test_api_submissions.py index e2a32906a..77110b287 100644 --- a/src/tests/api/test_api_submissions.py +++ b/src/tests/api/test_api_submissions.py @@ -57,6 +57,7 @@ def test_tag_serializer(tag): def test_submission_serializer_for_organiser(submission, orga_user, resource, tag): with scope(event=submission.event): submission.tags.add(tag) + submission.favourite_count = 3 data = SubmissionOrgaSerializer( submission, event=submission.event, @@ -88,6 +89,7 @@ def test_submission_serializer_for_organiser(submission, orga_user, resource, ta "resources", "tags", "tag_ids", + "favourite_count", } assert isinstance(data["speakers"], list) assert data["speakers"][0] == { From dc543f2ca48fb6e993cf2b89222d4c24125a8db1 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 16 Aug 2024 11:35:26 +0200 Subject: [PATCH 075/195] Allow schedule.js to show messages --- src/pretalx/agenda/templates/agenda/schedule.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pretalx/agenda/templates/agenda/schedule.html b/src/pretalx/agenda/templates/agenda/schedule.html index ed41003f4..77e5638d7 100644 --- a/src/pretalx/agenda/templates/agenda/schedule.html +++ b/src/pretalx/agenda/templates/agenda/schedule.html @@ -10,6 +10,16 @@ {% compress js %} {% endcompress %} + {% endblock %} {% block agenda_content %} From 8f97d02a5e38370675c1f0534b9df9d67ac3b89c Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 16 Aug 2024 12:19:17 +0200 Subject: [PATCH 076/195] Extract fav messages --- .../agenda/templates/agenda/schedule.html | 11 +-------- src/pretalx/agenda/urls.py | 5 ++++ src/pretalx/agenda/views/schedule.py | 23 +++++++++++++++++++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/pretalx/agenda/templates/agenda/schedule.html b/src/pretalx/agenda/templates/agenda/schedule.html index 77e5638d7..6da145196 100644 --- a/src/pretalx/agenda/templates/agenda/schedule.html +++ b/src/pretalx/agenda/templates/agenda/schedule.html @@ -10,16 +10,7 @@ {% compress js %} {% endcompress %} - + {% endblock %} {% block agenda_content %} diff --git a/src/pretalx/agenda/urls.py b/src/pretalx/agenda/urls.py index 98521ceaa..c31099830 100644 --- a/src/pretalx/agenda/urls.py +++ b/src/pretalx/agenda/urls.py @@ -56,6 +56,11 @@ def get_schedule_urls(regex_prefix, name_prefix=""): widget.widget_script, name="widget.script.legacy", ), + path( + "schedule/widget/messages.js", + schedule.schedule_messages, + name="widget.messages", + ), *get_schedule_urls("schedule"), *get_schedule_urls("schedule/v/", "versioned-"), path("sneak/", featured.sneakpeek_redirect, name="oldsneak"), diff --git a/src/pretalx/agenda/views/schedule.py b/src/pretalx/agenda/views/schedule.py index ea9f92753..082576b76 100644 --- a/src/pretalx/agenda/views/schedule.py +++ b/src/pretalx/agenda/views/schedule.py @@ -1,4 +1,5 @@ import hashlib +import json import logging import textwrap from contextlib import suppress @@ -16,6 +17,7 @@ from django.utils.functional import cached_property from django.utils.translation import activate from django.utils.translation import gettext_lazy as _ +from django.views.decorators.cache import cache_page from django.views.generic import TemplateView from django_context_decorator import context @@ -243,6 +245,27 @@ def show_talk_list(self): ) +@cache_page(60 * 60 * 24) +def schedule_messages(request, **kwargs): + """This view is cached for a day, as it is small and non-critical, but loaded synchronously.""" + strings = { + "favs_not_logged_in": _( + "You're currently not logged in, so your favourited talks can't be saved." + ), + "favs_not_loaded": _( + "Your favourites could not be loaded from the server, but they are stored locally in your browser." + ), + "favs_not_saved": _( + "Your favourites could not be saved on the server, but they are stored locally in your browser." + ), + } + strings = {key: str(value) for key, value in strings.items()} + return HttpResponse( + f"const PRETALX_MESSAGES = {json.dumps(strings)};", + content_type="application/javascript", + ) + + def talk_sort_key(talk): return (talk.start, talk.submission.title if talk.submission else "") From 1b17ec9b2aa9e6c02e5f23ef741bd8024f6565b1 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 16 Aug 2024 14:20:02 +0200 Subject: [PATCH 077/195] Add support for action permissions in API --- src/pretalx/api/permissions.py | 12 +++++++++--- src/pretalx/api/views/submission.py | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pretalx/api/permissions.py b/src/pretalx/api/permissions.py index 18e345a87..f07b3c2c6 100644 --- a/src/pretalx/api/permissions.py +++ b/src/pretalx/api/permissions.py @@ -6,10 +6,10 @@ class ApiPermission(BasePermission): def get_permission_object(self, view, obj, request, detail=False): if func := getattr(view, "get_permission_object", None): return func() - return obj or self.request.event + return obj or request.event def has_permission(self, request, view): - return self._has_permission(view, getattr(request, "event", None), request) + return self._has_permission(view, None, request) def has_object_permission(self, request, view, obj): return self._has_permission(view, obj, request) @@ -19,7 +19,13 @@ def _has_permission(self, view, obj, request): if not event: # Only true for root API view return True - permission_object = self.get_permission_object(view, obj, request) + permission_object = self.get_permission_object( + view, obj, request, detail=view.detail + ) + if permission_map := getattr(view, "permission_map", None): + suffix = "_object" if obj else "" + if permission_required := permission_map.get(f"{view.action}{suffix}"): + return request.user.has_perm(permission_required, permission_object) if request.method in SAFE_METHODS: read_permission = getattr(view, "read_permission_required", None) diff --git a/src/pretalx/api/views/submission.py b/src/pretalx/api/views/submission.py index 483109348..8b3e0834a 100644 --- a/src/pretalx/api/views/submission.py +++ b/src/pretalx/api/views/submission.py @@ -57,6 +57,10 @@ class SubmissionViewSet(viewsets.ReadOnlyModelViewSet): lookup_field = "code__iexact" search_fields = ("title", "speakers__name") filterset_class = SubmissionFilter + permission_map = { + "favourite": "agenda.view_schedule", + "favourite_object": "agenda.view_submission", + } def get_queryset(self): base_qs = self.request.event.submissions.all().annotate( From 14e8b2293713f9d3deb61954d39c4b9f8bb8459c Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 16 Aug 2024 14:20:35 +0200 Subject: [PATCH 078/195] Fixes in favourite API --- src/pretalx/api/views/submission.py | 6 +++--- src/pretalx/submission/models/submission.py | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/pretalx/api/views/submission.py b/src/pretalx/api/views/submission.py index 8b3e0834a..71a3564ad 100644 --- a/src/pretalx/api/views/submission.py +++ b/src/pretalx/api/views/submission.py @@ -111,14 +111,14 @@ def get_serializer(self, *args, **kwargs): ) @action(detail=False, methods=["GET"]) - def favourites(self, request): + def favourites(self, request, **kwargs): if not request.user.is_authenticated: raise Http404 return Response( [ sub.code for sub in Submission.objects.filter( - favourites__in=[request.user], event=request.event + favourites__user__in=[request.user], event=request.event ) ] ) @@ -129,7 +129,7 @@ def favourite(self, request, **kwargs): raise Http404 submission = self.get_object() if request.method == "POST": - submission.save_favourite(request.user) + submission.add_favourite(request.user) elif request.method == "DELETE": submission.remove_favourite(request.user) return Response({}) diff --git a/src/pretalx/submission/models/submission.py b/src/pretalx/submission/models/submission.py index 7ec3ff7c5..03322644c 100644 --- a/src/pretalx/submission/models/submission.py +++ b/src/pretalx/submission/models/submission.py @@ -939,6 +939,12 @@ def send_invite(self, to, _from=None, subject=None, text=None): send_invite.alters_data = True + def add_favourite(self, user): + SubmissionFavourite.objects.get_or_create(user=user, submission=self) + + def remove_favourite(self, user): + SubmissionFavourite.objects.filter(user=user, submission=self).delete() + class SubmissionFavouriteDeprecated(models.Model): user = models.OneToOneField( @@ -985,3 +991,4 @@ class SubmissionFavourite(PretalxModel): on_delete=models.CASCADE, related_name="favourites", ) + objects = ScopedManager(event="submission__event") From b99d7b0999c90d099c2fcde4a65d7a50e5a480b9 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 16 Aug 2024 14:38:03 +0200 Subject: [PATCH 079/195] Add missing unique constraint --- src/pretalx/submission/models/submission.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pretalx/submission/models/submission.py b/src/pretalx/submission/models/submission.py index 03322644c..e9eac4169 100644 --- a/src/pretalx/submission/models/submission.py +++ b/src/pretalx/submission/models/submission.py @@ -992,3 +992,6 @@ class SubmissionFavourite(PretalxModel): related_name="favourites", ) objects = ScopedManager(event="submission__event") + + class Meta: + unique_together = (("user", "submission"),) From 714a373670f3496d49f25659e5b7a4285f021c81 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 16 Aug 2024 16:52:45 +0200 Subject: [PATCH 080/195] Sync favs to API from detail page --- src/pretalx/agenda/templates/agenda/talk.html | 1 + src/pretalx/static/agenda/js/favourite.js | 102 +++++++++++------- 2 files changed, 66 insertions(+), 37 deletions(-) diff --git a/src/pretalx/agenda/templates/agenda/talk.html b/src/pretalx/agenda/templates/agenda/talk.html index f04798051..5c2a52a29 100644 --- a/src/pretalx/agenda/templates/agenda/talk.html +++ b/src/pretalx/agenda/templates/agenda/talk.html @@ -18,6 +18,7 @@ {% endblock %} {% block agenda_custom_header %} + {% compress js %} diff --git a/src/pretalx/static/agenda/js/favourite.js b/src/pretalx/static/agenda/js/favourite.js index 0033d70c1..872e99b9c 100644 --- a/src/pretalx/static/agenda/js/favourite.js +++ b/src/pretalx/static/agenda/js/favourite.js @@ -1,35 +1,33 @@ let isFaved = false let eventSlug = null let submissionId = null +let favButton = null +let loggedIn = false +let apiBaseUrl = null +let setupRun = false + +const spinStar = (star) => { + star.classList.add('fa-spin') + setTimeout(() => { + star.classList.remove('fa-spin') + }, 400) +} -// add argument "initial" defaulting to false const updateButton = (initial = false) => { - const favButton = document.getElementById('fav-button') - favButton.classList.remove('d-none') + // if "initial" is true, the star isn't animated + if (isFaved && !favButton.querySelector('.fa-star').classList.contains('d-none')) return + if (!isFaved && !favButton.querySelector('.fa-star-o').classList.contains('d-none')) return - if (isFaved && favButton.querySelector('.fa-star').classList.contains('d-none')) { - // Make the star rotate after appearing - favButton.querySelector('.fa-star-o').classList.add('d-none') - favButton.querySelector('.fa-star').classList.remove('d-none') - if (!initial) { - favButton.querySelector('.fa-star').classList.add('fa-spin') - setTimeout(() => { - favButton.querySelector('.fa-star').classList.remove('fa-spin') - }, 400) - } - } else if (!isFaved && favButton.querySelector('.fa-star-o').classList.contains('d-none')) { - favButton.querySelector('.fa-star').classList.add('d-none') - favButton.querySelector('.fa-star-o').classList.remove('d-none') - if (!initial) { - favButton.querySelector('.fa-star-o').classList.add('fa-spin') - setTimeout(() => { - favButton.querySelector('.fa-star-o').classList.remove('fa-spin') - }, 400) - } - } + let active = '.fa-star' + let inactive = '.fa-star' + + isFaved ? inactive = '.fa-star-o' : active = '.fa-star-o' + favButton.querySelector(active).classList.remove('d-none') + favButton.querySelector(inactive).classList.add('d-none') + if (!initial) spinStar(favButton.querySelector(active)) } -const loadFavs = () => { +const loadLocalFavs = () => { const data = localStorage.getItem(`${eventSlug}_favs`) let favs = [] if (data) { @@ -42,12 +40,30 @@ const loadFavs = () => { return favs } -const loadIsFaved = () => { - return loadFavs().includes(submissionId) +const apiFetch = async (path, method) => { + const headers = {'Content-Type': 'application/json'} + if (method === 'POST' || method === 'DELETE') { + headers['X-CSRFToken'] = document.cookie.split('pretalx_csrftoken=').pop().split(';').shift() + } + const response = await fetch(apiBaseUrl + path, { + method, + headers, + credentials: 'same-origin', + }) + return response.json() } -const saveIsFaved = () => { - let favs = loadFavs() +const loadIsFaved = async () => { + if (loggedIn) { + return await apiFetch(`submissions/favourites/`, 'GET').then(data => { + return data.includes(submissionId) + }).catch(() => {return loadLocalFavs().includes(submissionId)}) + } + return loadLocalFavs().includes(submissionId) +} + +const saveLocalFavs = () => { + let favs = loadLocalFavs() if (isFaved && !favs.includes(submissionId)) { favs.push(submissionId) } else if (!isFaved && favs.includes(submissionId)) { @@ -56,20 +72,32 @@ const saveIsFaved = () => { localStorage.setItem(`${eventSlug}_favs`, JSON.stringify(favs)) } -const toggleFavs = () => { - isFaved = loadIsFaved() +const toggleFavState = async () => { isFaved = !isFaved - saveIsFaved() + saveLocalFavs() updateButton() + if (loggedIn) { + await apiFetch(`submissions/${submissionId}/favourite/`, isFaved ? 'POST' : 'DELETE').catch() + } } -document.addEventListener('DOMContentLoaded', () => { - const favButton = document.getElementById('fav-button') - favButton.addEventListener('click', toggleFavs) - +const pageSetup = async () => { + setupRun = true + console.log('running page setup') eventSlug = window.location.pathname.split('/')[1] submissionId = window.location.pathname.split('/')[3] + loggedIn = document.querySelector('#pretalx-messages').dataset.loggedIn === 'true' + apiBaseUrl = window.location.origin + '/api/events/' + eventSlug + '/' - isFaved = loadIsFaved() + isFaved = await loadIsFaved() + favButton = document.getElementById('fav-button') + favButton.addEventListener('click', toggleFavState) + favButton.classList.remove('d-none') updateButton(true) -}) + + if (loggedIn) saveLocalFavs() +} + +document.addEventListener('DOMContentLoaded', pageSetup) +if (document.readyState === 'complete') pageSetup() +setTimeout(() => { if (!setupRun) pageSetup() }, 500) From f68f938f2ae29b5685bd7982f971120af942dd5e Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 16 Aug 2024 17:39:53 +0200 Subject: [PATCH 081/195] Add starred talk iCal export (logged-in users only) closes #1002 --- .../commands/export_schedule_html.py | 7 +++- src/pretalx/agenda/views/schedule.py | 17 ++------ src/pretalx/agenda/views/utils.py | 27 +++++++++++++ src/pretalx/api/views/submission.py | 3 ++ src/pretalx/common/exporter.py | 9 ++++- src/pretalx/orga/views/schedule.py | 6 +-- src/pretalx/orga/views/speaker.py | 6 +-- src/pretalx/schedule/exporters.py | 39 ++++++++++++++++++- src/pretalx/schedule/signals.py | 5 +++ src/tests/common/test_common_exporter.py | 2 +- 10 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 src/pretalx/agenda/views/utils.py diff --git a/src/pretalx/agenda/management/commands/export_schedule_html.py b/src/pretalx/agenda/management/commands/export_schedule_html.py index f8103cde7..56f672a27 100644 --- a/src/pretalx/agenda/management/commands/export_schedule_html.py +++ b/src/pretalx/agenda/management/commands/export_schedule_html.py @@ -71,7 +71,11 @@ def event_speaker_urls(event): def event_exporter_urls(event): for _, exporter in register_data_exporters.send(event): - if exporter.public: + # Skip exporters that are not public, and also skip exporters + # that dynamically determine if they are public, as we won't + # be able to serve dynamic content, and the risk of data leakage + # is too high. + if not hasattr(exporter, "is_public") and exporter.public: yield exporter(event).urls.base @@ -85,6 +89,7 @@ def schedule_version_urls(event): def event_urls(event): yield event.urls.base yield event.urls.schedule + yield event.urls.schedule + "widget/messages.js" yield event.urls.schedule_nojs yield event.urls.schedule_widget_data yield from schedule_version_urls(event) diff --git a/src/pretalx/agenda/views/schedule.py b/src/pretalx/agenda/views/schedule.py index 082576b76..c6cc4e217 100644 --- a/src/pretalx/agenda/views/schedule.py +++ b/src/pretalx/agenda/views/schedule.py @@ -21,7 +21,7 @@ from django.views.generic import TemplateView from django_context_decorator import context -from pretalx.common.signals import register_data_exporters +from pretalx.agenda.views.utils import find_schedule_exporter, get_schedule_exporters from pretalx.common.signals import register_my_data_exporters from pretalx.common.text.path import safe_filename from pretalx.common.views.mixins import EventPermissionRequired @@ -97,13 +97,7 @@ def get_exporter(self, request): exporter = ( exporter[len("export.") :] if exporter.startswith("export.") else exporter ) - responses = register_data_exporters.send( - request.event - ) + register_my_data_exporters.send(request.event) - for __, response in responses: - ex = response(request.event) - if ex.identifier == exporter and (ex.public or request.is_orga): - return ex + return find_schedule_exporter(request, exporter) def get(self, request, *args, **kwargs): exporter = self.get_exporter(request) @@ -128,7 +122,7 @@ def get(self, request, *args, **kwargs): exporter.is_orga = getattr(self.request, "is_orga", False) try: - file_name, file_type, data = exporter.render() + file_name, file_type, data = exporter.render(request=request) etag = hashlib.sha1(str(data).encode()).hexdigest() except Exception: logger.exception( @@ -225,10 +219,7 @@ def get_object(self): @context def exporters(self): - return [ - exporter(self.request.event) - for _, exporter in register_data_exporters.send(self.request.event) - ] + return get_schedule_exporters(self.request) @context def my_exporters(self): diff --git a/src/pretalx/agenda/views/utils.py b/src/pretalx/agenda/views/utils.py new file mode 100644 index 000000000..1e01a9f32 --- /dev/null +++ b/src/pretalx/agenda/views/utils.py @@ -0,0 +1,27 @@ +from pretalx.common.signals import register_data_exporters + + +def get_schedule_exporters(request): + exporters = [ + exporter(request.event) + for _, exporter in register_data_exporters.send(request.event) + ] + result = [] + for exporter in exporters: + if hasattr(exporter, "is_public"): + try: + response = exporter.is_public(request=request) + if response: + result.append(exporter) + except Exception: + pass + elif exporter.public or request.is_orga: + result.append(exporter) + return result + + +def find_schedule_exporter(request, name): + for exporter in get_schedule_exporters(request): + if exporter.identifier == name: + return exporter + return None diff --git a/src/pretalx/api/views/submission.py b/src/pretalx/api/views/submission.py index 71a3564ad..eca72521f 100644 --- a/src/pretalx/api/views/submission.py +++ b/src/pretalx/api/views/submission.py @@ -114,6 +114,9 @@ def get_serializer(self, *args, **kwargs): def favourites(self, request, **kwargs): if not request.user.is_authenticated: raise Http404 + # Return ical file if accept header is set to text/calendar + if request.accepted_renderer.format == "ics": + return self.favourites_ical(request) return Response( [ sub.code diff --git a/src/pretalx/common/exporter.py b/src/pretalx/common/exporter.py index 20a0b60fc..e5657d220 100644 --- a/src/pretalx/common/exporter.py +++ b/src/pretalx/common/exporter.py @@ -47,7 +47,12 @@ def quoted_identifier(self) -> str: @property def public(self) -> bool: """Return True if the exported data should be publicly available once - the event is public, False otherwise.""" + the event is public, False otherwise. + + If you need additional data to decide, you can instead implement the + ``is_public(self, request, **kwargs)`` method, which overrides this + property. + """ raise NotImplementedError() # NOQA @property @@ -81,7 +86,7 @@ def group(self) -> str: """ return "submission" - def render(self, **kwargs) -> tuple[str, str, str]: + def render(self, request, **kwargs) -> tuple[str, str, str]: """Render the exported file and return a tuple consisting of a file name, a file type and file content.""" raise NotImplementedError() # NOQA diff --git a/src/pretalx/orga/views/schedule.py b/src/pretalx/orga/views/schedule.py index 03ce94b2a..322cb5b04 100644 --- a/src/pretalx/orga/views/schedule.py +++ b/src/pretalx/orga/views/schedule.py @@ -22,8 +22,8 @@ from pretalx.agenda.management.commands.export_schedule_html import get_export_zip_path from pretalx.agenda.tasks import export_schedule_html +from pretalx.agenda.views.utils import get_schedule_exporters from pretalx.common.language import get_current_language_information -from pretalx.common.signals import register_data_exporters from pretalx.common.text.path import safe_filename from pretalx.common.text.phrases import phrases from pretalx.common.views import CreateOrUpdateView, OrderModelView @@ -103,8 +103,8 @@ def get_form_kwargs(self): @context def exporters(self): return [ - exporter(self.request.event) - for _, exporter in register_data_exporters.send(self.request.event) + exporter + for exporter in get_schedule_exporters(self.request) if exporter.group != "speaker" ] diff --git a/src/pretalx/orga/views/speaker.py b/src/pretalx/orga/views/speaker.py index dce8dda9a..d7b0f1cf0 100644 --- a/src/pretalx/orga/views/speaker.py +++ b/src/pretalx/orga/views/speaker.py @@ -10,8 +10,8 @@ from django.views.generic import DetailView, FormView, ListView, View from django_context_decorator import context +from pretalx.agenda.views.utils import get_schedule_exporters from pretalx.common.exceptions import SendMailException -from pretalx.common.signals import register_data_exporters from pretalx.common.text.phrases import phrases from pretalx.common.views import CreateOrUpdateView from pretalx.common.views.mixins import ( @@ -358,8 +358,8 @@ def get_form_kwargs(self): @context def exporters(self): return [ - exporter(self.request.event) - for _, exporter in register_data_exporters.send(self.request.event) + exporter + for exporter in get_schedule_exporters(self.request) if exporter.group == "speaker" ] diff --git a/src/pretalx/schedule/exporters.py b/src/pretalx/schedule/exporters.py index 62757def7..8fdbb97c3 100644 --- a/src/pretalx/schedule/exporters.py +++ b/src/pretalx/schedule/exporters.py @@ -5,9 +5,11 @@ from zoneinfo import ZoneInfo import vobject +from django.conf import settings from django.template.loader import get_template from django.utils.functional import cached_property from django.utils.safestring import SafeString +from django.utils.translation import gettext_lazy as _ from i18nfield.utils import I18nJSONEncoder from pretalx import __version__ @@ -347,7 +349,7 @@ class MyFrabJsonExporter(FrabJsonExporter): class ICalExporter(BaseExporter): identifier = "schedule.ics" - verbose_name = "iCal" + verbose_name = _("iCal (full event)") public = False show_qrcode = True favs_retrieve = False @@ -383,3 +385,38 @@ class MyICalExporter(ICalExporter): identifier = "schedule-my.ics" verbose_name = "My ⭐ Sessions iCal" favs_retrieve = True +class FavedICalExporter(BaseExporter): + identifier = "faved.ics" + verbose_name = _("iCal (your starred sessions)") + public = False + show_qrcode = False + icon = "fa-calendar" + cors = "*" + + def is_public(self, request, **kwargs): + return ( + "agenda" in request.resolver_match.namespaces + and request.user.is_authenticated + and request.user.has_perm("agenda.view_schedule") + ) + + def render(self, request, **kwargs): + if not request.user.is_authenticated: + return None + + netloc = urlparse(settings.SITE_URL).netloc + submissions = ( + request.event.submissions.filter(favourites__user__in=[request.user]) + .select_related("room") + .prefetch_related("speakers") + ) + + cal = vobject.iCalendar() + cal.add("prodid").value = f"-//pretalx//{netloc}//{request.event.slug}//faved" + + for submission in submissions: + for slot in submission.slots.filter( + schedule=request.event.current_schedule + ): + slot.build_ical(cal) + return f"{self.event.slug}-favs.ics", "text/calendar", cal.serialize() diff --git a/src/pretalx/schedule/signals.py b/src/pretalx/schedule/signals.py index f0b78b303..74475917c 100644 --- a/src/pretalx/schedule/signals.py +++ b/src/pretalx/schedule/signals.py @@ -31,6 +31,11 @@ def register_my_ical_exporter(sender, **kwargs): from .exporters import MyICalExporter return MyICalExporter +@receiver(register_data_exporters, dispatch_uid="exporter_builtin_faved_ical") +def register_faved_ical_exporter(sender, **kwargs): + from pretalx.schedule.exporters import FavedICalExporter + + return FavedICalExporter @receiver(register_data_exporters, dispatch_uid="exporter_builtin_xml") diff --git a/src/tests/common/test_common_exporter.py b/src/tests/common/test_common_exporter.py index 692970a03..01759d8f3 100644 --- a/src/tests/common/test_common_exporter.py +++ b/src/tests/common/test_common_exporter.py @@ -14,7 +14,7 @@ def test_common_base_exporter_raises_proper_exceptions(): with pytest.raises(NotImplementedError): exporter.icon with pytest.raises(NotImplementedError): - exporter.render() + exporter.render(None) with pytest.raises(NotImplementedError): str(exporter) assert exporter.cors is None From 46f62a0af56d6d611dc463b8dddb4694de56ae1e Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 16 Aug 2024 17:48:41 +0200 Subject: [PATCH 082/195] Fix exporter visibility --- src/pretalx/agenda/templates/agenda/header_row.html | 2 +- src/pretalx/agenda/views/schedule.py | 2 +- src/pretalx/agenda/views/utils.py | 4 ++-- src/pretalx/schedule/exporters.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pretalx/agenda/templates/agenda/header_row.html b/src/pretalx/agenda/templates/agenda/header_row.html index 1f3ca0509..81373ada0 100644 --- a/src/pretalx/agenda/templates/agenda/header_row.html +++ b/src/pretalx/agenda/templates/agenda/header_row.html @@ -87,7 +87,7 @@

    + {% include "orga/includes/submit_row.html" %} + {% endblock %} diff --git a/src/pretalx/orga/templates/orga/cfp/question_remind.html b/src/pretalx/orga/templates/orga/cfp/question_remind.html index a65e2c9c7..1f7a68deb 100644 --- a/src/pretalx/orga/templates/orga/cfp/question_remind.html +++ b/src/pretalx/orga/templates/orga/cfp/question_remind.html @@ -1,13 +1,12 @@ {% extends "orga/base.html" %} + {% load bootstrap4 %} {% load i18n %} {% block extra_title %}{% translate "Send out reminders" %} :: {% endblock %} {% block content %} -

    - {% translate "Send out reminders" %} -

    +

    {% translate "Send out reminders" %}

    {% blocktranslate trimmed %} @@ -19,6 +18,8 @@

    {% csrf_token %} {% bootstrap_form filter_form layout='event' %} + {% include "orga/includes/submit_row.html" with submit_label=phrases.base.send %} +
    {% endblock %} diff --git a/src/pretalx/orga/templates/orga/cfp/question_view.html b/src/pretalx/orga/templates/orga/cfp/question_view.html index e4a3935f9..78b7e08cf 100644 --- a/src/pretalx/orga/templates/orga/cfp/question_view.html +++ b/src/pretalx/orga/templates/orga/cfp/question_view.html @@ -1,4 +1,5 @@ {% extends "orga/base.html" %} + {% load i18n %} {% load static %} @@ -55,13 +56,9 @@

    {% for question in questions %} - - {{ question.question }} - - - - {{ question.get_target_display }} + {{ question.question }} + {{ question.get_target_display }} @@ -73,10 +70,9 @@

    - - {{ question.answer_count }} - + {{ question.answer_count }} + {% include "orga/includes/order_object.html" with object=question %} diff --git a/src/pretalx/orga/templates/orga/cfp/submission_type_form.html b/src/pretalx/orga/templates/orga/cfp/submission_type_form.html index 041798bad..81a17c85e 100644 --- a/src/pretalx/orga/templates/orga/cfp/submission_type_form.html +++ b/src/pretalx/orga/templates/orga/cfp/submission_type_form.html @@ -1,4 +1,5 @@ {% extends "orga/base.html" %} + {% load bootstrap4 %} {% load compress %} {% load i18n %} @@ -30,7 +31,9 @@

    {% bootstrap_field form.default_duration addon_after="minutes" layout='event' addon_after_class="input-group-append input-group-text" %} {% bootstrap_field form.deadline layout='event' %} {% bootstrap_field form.requires_access_code layout='event' %} + {% include "orga/includes/submit_row.html" %} + {% endblock %} diff --git a/src/pretalx/orga/templates/orga/cfp/submission_type_view.html b/src/pretalx/orga/templates/orga/cfp/submission_type_view.html index c86ca4775..02242154c 100644 --- a/src/pretalx/orga/templates/orga/cfp/submission_type_view.html +++ b/src/pretalx/orga/templates/orga/cfp/submission_type_view.html @@ -1,4 +1,5 @@ {% extends "orga/base.html" %} + {% load i18n %} {% load static %} @@ -48,36 +49,27 @@

    {% if type.requires_access_code %} {% endif %} - - {{ type.name }} - + {{ type.name }} - - {{ type.submissions.all.count }} - + {{ type.submissions.all.count }} {{ type.default_duration }} {% if request.event.cfp.default_type == type %} {% translate "Default" %} {% else %} - - Make default - + Make default {% endif %} - + - + @@ -86,5 +78,7 @@

    + {% include "orga/includes/pagination.html" %} + {% endblock %} diff --git a/src/pretalx/orga/templates/orga/cfp/text.html b/src/pretalx/orga/templates/orga/cfp/text.html index 25fd2ed2c..5a297242b 100644 --- a/src/pretalx/orga/templates/orga/cfp/text.html +++ b/src/pretalx/orga/templates/orga/cfp/text.html @@ -1,4 +1,5 @@ {% extends "orga/base.html" %} + {% load bootstrap4 %} {% load compress %} {% load i18n %} @@ -28,14 +29,16 @@

    - {% translate "A good Call for Participation will engage potential speakers. Remember to include:" %}
    + {% translate "A good Call for Participation will engage potential speakers. Remember to include:" %} +
    • {% translate "The formats (sessions, workshops, panels) and their durations" %}
    • {% translate "Topics you are looking for" %}
    • {% translate "How open you are to alternative topics" %}
    • {% translate "The people coming to your conference: interests, experience level …" %}
    • {% translate "Link your Code of Conduct and Data Protection statements." %}
    • -
    • {% translate "Do you offer financial or other support, e.g. support for first time speakers?" %} +
    • + {% translate "Do you offer financial or other support, e.g. support for first time speakers?" %}
    • {% translate "Dates and location" %}
    @@ -240,6 +243,7 @@

    {% include "orga/includes/submit_row.html" %} +

    diff --git a/src/pretalx/orga/templates/orga/cfp/track_form.html b/src/pretalx/orga/templates/orga/cfp/track_form.html index 471d50271..06ebdafa4 100644 --- a/src/pretalx/orga/templates/orga/cfp/track_form.html +++ b/src/pretalx/orga/templates/orga/cfp/track_form.html @@ -1,11 +1,12 @@ {% extends "orga/base.html" %} + {% load bootstrap4 %} {% load compress %} {% load i18n %} {% load static %} {% block stylesheets %} - + {% endblock %} {% block scripts %} @@ -33,7 +34,9 @@

    {% bootstrap_field form.description layout='event' %} {% bootstrap_field form.color layout='event' addon_before="" addon_before_class="colorpicker-input-addon color-visible" %} {% bootstrap_field form.requires_access_code layout='event' %} + {% include "orga/includes/submit_row.html" %} + {% endblock %} diff --git a/src/pretalx/orga/templates/orga/cfp/track_view.html b/src/pretalx/orga/templates/orga/cfp/track_view.html index dac74ac73..185437899 100644 --- a/src/pretalx/orga/templates/orga/cfp/track_view.html +++ b/src/pretalx/orga/templates/orga/cfp/track_view.html @@ -1,4 +1,5 @@ {% extends "orga/base.html" %} + {% load i18n %} {% load static %} @@ -45,18 +46,16 @@

    {% for track in tracks %} - - {{ track.name }} - + {{ track.name }} {% if track.requires_access_code %} {% endif %} -
    + +
    + - - {{ track.submissions.all.count }} - + {{ track.submissions.all.count }} class="btn btn-sm btn-info"> + {% include "orga/includes/order_object.html" with object=track %} - + + - + @@ -79,5 +78,7 @@

    + {% include "orga/includes/pagination.html" %} + {% endblock %} diff --git a/src/pretalx/orga/templates/orga/event/dashboard.html b/src/pretalx/orga/templates/orga/event/dashboard.html index ab7f473e4..93709bb23 100644 --- a/src/pretalx/orga/templates/orga/event/dashboard.html +++ b/src/pretalx/orga/templates/orga/event/dashboard.html @@ -1,4 +1,5 @@ {% extends "orga/base.html" %} + {% load i18n %} {% load rules %} @@ -100,6 +101,7 @@

    {{ tile.large }}

    {% translate "History" %}

    {% if can_change_settings %}({% translate "Full history" %}){% endif %}

    {% include "common/logs.html" with entries=history %} +
    {% endif %} {% endblock %} diff --git a/src/pretalx/orga/templates/orga/event/history.html b/src/pretalx/orga/templates/orga/event/history.html index 057cf1fb2..15dca5817 100644 --- a/src/pretalx/orga/templates/orga/event/history.html +++ b/src/pretalx/orga/templates/orga/event/history.html @@ -1,4 +1,5 @@ {% extends "orga/base.html" %} + {% load i18n %} {% block extra_title %}{% translate "Event History" %} :: {% endblock %} @@ -6,7 +7,9 @@ {% block content %}

    {% translate "Event History" %}

    + {% include "common/logs.html" with entries=log_entries %} {% include "orga/includes/pagination.html" %} +
    {% endblock %} diff --git a/src/pretalx/orga/templates/orga/event/live.html b/src/pretalx/orga/templates/orga/event/live.html index 00187e555..4c4535d51 100644 --- a/src/pretalx/orga/templates/orga/event/live.html +++ b/src/pretalx/orga/templates/orga/event/live.html @@ -1,10 +1,17 @@ {% extends "orga/base.html" %} + {% load i18n %} {% block extra_title %}{% if request.event.is_public %}{% translate "Deactivate event" %}{% else %}{% translate "Go live" %}{% endif %} :: {% endblock %} {% block content %} -

    {% if request.event.is_public %}{% translate "Deactivate event" %}{% else %}{% translate "Go live" %}{% endif %}

    +

    + {% if request.event.is_public %} + {% translate "Deactivate event" %} + {% else %} + {% translate "Go live" %} + {% endif %} +

    {% if request.event.is_public %} {% blocktranslate trimmed %} @@ -21,7 +28,11 @@

    {% if request.event.is_public %}{% translate "Deactivate event" %}{% else %}

    {% translate "Your event may not be ready for release yet!" %}

    {% endif %} @@ -29,7 +40,11 @@

    {% translate "Your event may not be ready for release yet!" %}

    {% translate "There may be easy ways to improve your event before its release!" %}

    {% endif %} diff --git a/src/pretalx/orga/templates/orga/event/wizard/base.html b/src/pretalx/orga/templates/orga/event/wizard/base.html index 10c45b0de..caee5602e 100644 --- a/src/pretalx/orga/templates/orga/event/wizard/base.html +++ b/src/pretalx/orga/templates/orga/event/wizard/base.html @@ -1,4 +1,5 @@ {% extends "orga/base.html" %} + {% load bootstrap4 %} {% load i18n %} diff --git a/src/pretalx/orga/templates/orga/event/wizard/copy.html b/src/pretalx/orga/templates/orga/event/wizard/copy.html index d9e53d06a..d491eb7e6 100644 --- a/src/pretalx/orga/templates/orga/event/wizard/copy.html +++ b/src/pretalx/orga/templates/orga/event/wizard/copy.html @@ -1,4 +1,5 @@ {% extends "orga/event/wizard/base.html" %} + {% load i18n %} {% block wizard_content %} diff --git a/src/pretalx/orga/templates/orga/event/wizard/display.html b/src/pretalx/orga/templates/orga/event/wizard/display.html index a8e84970b..4d82ba565 100644 --- a/src/pretalx/orga/templates/orga/event/wizard/display.html +++ b/src/pretalx/orga/templates/orga/event/wizard/display.html @@ -1,10 +1,11 @@ {% extends "orga/event/wizard/base.html" %} + {% load compress %} {% load i18n %} {% load static %} {% block stylesheets %} - + {% endblock %} {% block scripts %} diff --git a/src/pretalx/orga/templates/orga/event/wizard/initial.html b/src/pretalx/orga/templates/orga/event/wizard/initial.html index e88be69bc..58c0c3d8f 100644 --- a/src/pretalx/orga/templates/orga/event/wizard/initial.html +++ b/src/pretalx/orga/templates/orga/event/wizard/initial.html @@ -1,2 +1,3 @@ {% extends "orga/event/wizard/base.html" %} + {% load i18n %} diff --git a/src/pretalx/orga/templates/orga/event_list.html b/src/pretalx/orga/templates/orga/event_list.html index 448403db4..db705f7c2 100644 --- a/src/pretalx/orga/templates/orga/event_list.html +++ b/src/pretalx/orga/templates/orga/event_list.html @@ -1,4 +1,5 @@ {% extends "orga/base.html" %} + {% load i18n %} {% load rules %} @@ -76,7 +77,11 @@

    {{ event.name }}

    {% if event.is_public %}
    - {% if event.is_open %}{% translate "CfP open" %}{% else %}{% translate "live" %}{% endif %} + {% if event.is_open %} + {% translate "CfP open" %} + {% else %} + {% translate "live" %} + {% endif %}
    {% else %}
    {% translate "Not public" %}
    diff --git a/src/pretalx/orga/templates/orga/includes/order_object.html b/src/pretalx/orga/templates/orga/includes/order_object.html index 17f3a0a66..f4e010bd9 100644 --- a/src/pretalx/orga/templates/orga/includes/order_object.html +++ b/src/pretalx/orga/templates/orga/includes/order_object.html @@ -1,4 +1,5 @@ {% load i18n %} + {% if not forloop.last %} 1 %}