From 5137d86fc8922217ae53fdd35689e69a7ebefbca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=A4hr?= Date: Fri, 1 Mar 2024 16:57:01 +0300 Subject: [PATCH 1/2] second draft to show additional proposal questions on the proposal form --- src/pretalx/cfp/flow.py | 40 +++++------ .../cfp/event/submission_profile.html | 1 + src/pretalx/submission/forms/submission.py | 71 ++++++++++++++++++- 3 files changed, 86 insertions(+), 26 deletions(-) diff --git a/src/pretalx/cfp/flow.py b/src/pretalx/cfp/flow.py index 1ff293c50..435d15e93 100644 --- a/src/pretalx/cfp/flow.py +++ b/src/pretalx/cfp/flow.py @@ -19,6 +19,9 @@ from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ from django.views.generic.base import TemplateResponseMixin + +from django_context_decorator import context + from i18nfield.strings import LazyI18nString from i18nfield.utils import I18nJSONEncoder @@ -26,6 +29,7 @@ from pretalx.common.exceptions import SendMailException from pretalx.common.phrases import phrases from pretalx.common.utils import language +from pretalx.common.views import is_form_bound from pretalx.person.forms import SpeakerProfileForm, UserForm from pretalx.person.models import User from pretalx.submission.forms import InfoForm, QuestionsForm @@ -407,29 +411,7 @@ def _text(self): ) def is_applicable(self, request): - self.request = request - info_data = self.cfp_session.get("data", {}).get("info", {}) - track = info_data.get("track") - if track: - questions = self.event.questions.exclude( - Q(target=QuestionTarget.SUBMISSION) - & ( - (~Q(tracks__in=[info_data.get("track")]) & Q(tracks__isnull=False)) - | ( - ~Q(submission_types__in=[info_data.get("submission_type")]) - & Q(submission_types__isnull=False) - ) - ) - ) - else: - questions = self.event.questions.exclude( - Q(target=QuestionTarget.SUBMISSION) - & ( - ~Q(submission_types__in=[info_data.get("submission_type")]) - & Q(submission_types__isnull=False) - ) - ) - return questions.exists() + return False def get_form_kwargs(self): result = super().get_form_kwargs() @@ -537,6 +519,18 @@ def get_context_data(self, **kwargs): result["gravatar_parameter"] = User(email=email).gravatar_parameter return result + @context + @cached_property + def questions_form(self): + bind = is_form_bound(self.request, "questions") + return QuestionsForm( + data=self.request.POST if bind else None, + files=self.request.FILES if bind else None, + speaker=self.request.user, + event=self.request.event, + target="speaker", + ) + def done(self, request, draft=False): form = self.get_form(from_storage=True) form.is_valid() diff --git a/src/pretalx/cfp/templates/cfp/event/submission_profile.html b/src/pretalx/cfp/templates/cfp/event/submission_profile.html index 7141633a4..f17ea9930 100644 --- a/src/pretalx/cfp/templates/cfp/event/submission_profile.html +++ b/src/pretalx/cfp/templates/cfp/event/submission_profile.html @@ -24,6 +24,7 @@

{{ title }}

{% bootstrap_field form.name layout='event' %} {% if form.biography %}{% bootstrap_field form.biography layout='event' %}{% endif %} + {% if questions_form %}{% bootstrap_form questions_form layout='event' %}{% endif %} {% if form.availabilities %} {% compress js %} diff --git a/src/pretalx/submission/forms/submission.py b/src/pretalx/submission/forms/submission.py index ae79cad74..54343f182 100644 --- a/src/pretalx/submission/forms/submission.py +++ b/src/pretalx/submission/forms/submission.py @@ -1,5 +1,6 @@ from django import forms from django.db.models import Count, Exists, OuterRef, Q +from django.utils.functional import cached_property from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ from django_scopes.forms import SafeModelChoiceField @@ -7,13 +8,13 @@ from pretalx.cfp.forms.cfp import CfPFormMixin from pretalx.common.forms.fields import ImageField from pretalx.common.forms.widgets import MarkdownWidget -from pretalx.common.mixins.forms import PublicContent, RequestRequire +from pretalx.common.mixins.forms import PublicContent, RequestRequire, QuestionFieldsMixin from pretalx.common.mixins.views import Filterable from pretalx.submission.forms.track_select_widget import TrackSelectWidget -from pretalx.submission.models import Answer, Question, Submission, SubmissionStates +from pretalx.submission.models import Answer, Question, Submission, SubmissionStates, QuestionTarget, QuestionVariant -class InfoForm(CfPFormMixin, RequestRequire, PublicContent, forms.ModelForm): +class InfoForm(CfPFormMixin, RequestRequire, PublicContent, QuestionFieldsMixin, forms.ModelForm): additional_speaker = forms.EmailField( label=_("Additional Speaker"), help_text=_( @@ -30,6 +31,13 @@ class InfoForm(CfPFormMixin, RequestRequire, PublicContent, forms.ModelForm): def __init__(self, event, **kwargs): self.event = event + self.submission = kwargs.pop("submission", None) + self.track = kwargs.pop("track", None) or getattr( + self.submission, "track", None + ) + self.submission_type = kwargs.pop("submission_type", None) or getattr( + self.submission, "submission_type", None + ) self.readonly = kwargs.pop("readonly", False) self.access_code = kwargs.pop("access_code", None) self.default_values = {} @@ -60,6 +68,55 @@ def __init__(self, event, **kwargs): for f in self.fields.values(): f.disabled = True + self.target_type = kwargs.pop("target", QuestionTarget.SUBMISSION) + self.for_reviewers = kwargs.pop("for_reviewers", False) + if self.target_type == QuestionTarget.SUBMISSION: + target_object = self.submission + elif self.target_type == QuestionTarget.SPEAKER: + target_object = self.speaker + + self.queryset = Question.all_objects.filter(event=self.event, active=True) + if self.target_type: + self.queryset = self.queryset.filter(target=self.target_type) + else: + self.queryset = self.queryset.exclude(target=QuestionTarget.REVIEWER) + if self.track: + self.queryset = self.queryset.filter( + Q(tracks__in=[self.track]) | Q(tracks__isnull=True) + ) + if self.submission_type: + self.queryset = self.queryset.filter( + Q(submission_types__in=[self.submission_type]) + | Q(submission_types__isnull=True) + ) + + for question in self.queryset.prefetch_related("options"): + initial_object = None + initial = question.default_answer + if target_object: + answers = [ + a + for a in target_object.answers.all() + if a.question_id == question.id + ] + if answers: + initial_object = answers[0] + initial = ( + answers[0].answer_file + if question.variant == QuestionVariant.FILE + else answers[0].answer + ) + + field = self.get_field( + question=question, + initial=initial, + initial_object=initial_object, + readonly=self.readonly, + ) + field.question = question + field.answer = initial_object + self.fields[f"question_{question.pk}"] = field + def _set_track(self, instance=None): if "track" in self.fields: if ( @@ -160,6 +217,14 @@ def _set_slot_count(self, instance=None): ) ) + @cached_property + def submission_fields(self): + return [ + forms.BoundField(self, field, name) + for name, field in self.fields.items() + if hasattr(field, "question") and field.question.target == QuestionTarget.SUBMISSION + ] + def save(self, *args, **kwargs): for key, value in self.default_values.items(): setattr(self.instance, key, value) From 0dfdcc9062daf10de9da7bd4ca9928b3d48a53b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C3=A4hr?= Date: Sun, 17 Mar 2024 01:03:51 +0300 Subject: [PATCH 2/2] convert avatar_source and avatar_license to TextField with markdown. TODO: fix length enforcement --- src/pretalx/orga/forms/cfp.py | 2 +- src/pretalx/orga/templates/orga/cfp/text.html | 8 ++++---- src/pretalx/person/forms.py | 4 ++++ .../0031_alter_user_avatar_license.py | 18 ++++++++++++++++++ .../0032_alter_user_avatar_source.py | 18 ++++++++++++++++++ src/pretalx/person/models/user.py | 6 ++---- 6 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 src/pretalx/person/migrations/0031_alter_user_avatar_license.py create mode 100644 src/pretalx/person/migrations/0032_alter_user_avatar_source.py diff --git a/src/pretalx/orga/forms/cfp.py b/src/pretalx/orga/forms/cfp.py index 0d288014c..6a5e7aeb0 100644 --- a/src/pretalx/orga/forms/cfp.py +++ b/src/pretalx/orga/forms/cfp.py @@ -53,7 +53,7 @@ def __init__(self, *args, obj, **kwargs): self.fields[ "mail_on_new_submission" ].help_text += f' ({obj.email})' - self.length_fields = ["title", "abstract", "description", "biography"] + self.length_fields = ["title", "abstract", "description", "biography", "avatar_source", "avatar_license"] self.request_require_fields = [ "abstract", "description", diff --git a/src/pretalx/orga/templates/orga/cfp/text.html b/src/pretalx/orga/templates/orga/cfp/text.html index 9569799da..cd367c7cd 100644 --- a/src/pretalx/orga/templates/orga/cfp/text.html +++ b/src/pretalx/orga/templates/orga/cfp/text.html @@ -169,14 +169,14 @@ {% translate "Profile picture source" %} {% bootstrap_field sform.cfp_ask_avatar_source layout='event-inline' %} - - + {% bootstrap_field sform.cfp_avatar_source_min_length use_label=False layout='inline' %} + {% bootstrap_field sform.cfp_avatar_source_max_length use_label=False layout='inline' %} {% translate "Profile picture license" %} {% bootstrap_field sform.cfp_ask_avatar_license layout='event-inline' %} - - + {% bootstrap_field sform.cfp_avatar_license_min_length use_label=False layout='inline' %} + {% bootstrap_field sform.cfp_avatar_license_max_length use_label=False layout='inline' %} {% translate "Biography" %} diff --git a/src/pretalx/person/forms.py b/src/pretalx/person/forms.py index 66bfb2507..0ee1b9b09 100644 --- a/src/pretalx/person/forms.py +++ b/src/pretalx/person/forms.py @@ -198,6 +198,8 @@ def __init__(self, *args, name=None, **kwargs): self.fields[field] = field_class( initial=initial.get(field), disabled=read_only ) + if self.Meta.widgets.get(field): + self.fields[field].widget = self.Meta.widgets.get(field)() self._update_cfp_texts(field) if not self.event.cfp.request_avatar: @@ -276,6 +278,8 @@ class Meta: } widgets = { "biography": MarkdownWidget, + "avatar_source": MarkdownWidget, + "avatar_license": MarkdownWidget, } request_require = {"biography", "availabilities"} diff --git a/src/pretalx/person/migrations/0031_alter_user_avatar_license.py b/src/pretalx/person/migrations/0031_alter_user_avatar_license.py new file mode 100644 index 000000000..2a7e2134c --- /dev/null +++ b/src/pretalx/person/migrations/0031_alter_user_avatar_license.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.11 on 2024-03-16 15:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("person", "0030_user_avatar_source_alter_user_avatar_license"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="avatar_license", + field=models.TextField(null=True), + ), + ] diff --git a/src/pretalx/person/migrations/0032_alter_user_avatar_source.py b/src/pretalx/person/migrations/0032_alter_user_avatar_source.py new file mode 100644 index 000000000..77b05f645 --- /dev/null +++ b/src/pretalx/person/migrations/0032_alter_user_avatar_source.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.11 on 2024-03-16 22:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("person", "0031_alter_user_avatar_license"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="avatar_source", + field=models.TextField(null=True), + ), + ] diff --git a/src/pretalx/person/models/user.py b/src/pretalx/person/models/user.py index bbf9f8ccc..b48ba2fb1 100644 --- a/src/pretalx/person/models/user.py +++ b/src/pretalx/person/models/user.py @@ -128,19 +128,17 @@ class User(PermissionsMixin, GenerateCode, FileCleanupMixin, AbstractBaseUser): "If you have registered with an email address that has a gravatar account, we can retrieve your profile picture from there." ), ) - avatar_source = models.CharField( + avatar_source = models.TextField( null=True, blank=True, - max_length=999, verbose_name=_("Profile Picture Source"), help_text=_( "Please enter the name of the author or source of image and a link if applicable." ), ) - avatar_license = models.CharField( + avatar_license = models.TextField( null=True, blank=True, - max_length=999, verbose_name=_("Profile Picture License"), help_text=_( "Please enter the name of the license of the photo and link to it if applicable."