diff --git a/src/agenda_culturel/forms/__init__.py b/src/agenda_culturel/forms/__init__.py new file mode 100644 index 0000000..15fa6e6 --- /dev/null +++ b/src/agenda_culturel/forms/__init__.py @@ -0,0 +1,9 @@ +from .utils import * +from .user import * +from .category import * +from .tag import * +from .event import * +from .place import * +from .message import * +from .imports import * +from .special_period import * diff --git a/src/agenda_culturel/forms/category.py b/src/agenda_culturel/forms/category.py new file mode 100644 index 0000000..a5203e8 --- /dev/null +++ b/src/agenda_culturel/forms/category.py @@ -0,0 +1,57 @@ +import logging + +from django.forms import ( + BooleanField, + CharField, + Form, + HiddenInput, + ModelForm, +) +from django.utils.translation import gettext_lazy as _ + +from ..models import ( + CategorisationRule, + Event, +) + +logger = logging.getLogger(__name__) + + +class CategorisationRuleImportForm(ModelForm): + required_css_class = "required" + + class Meta: + model = CategorisationRule + fields = "__all__" + + +class CategorisationForm(Form): + required_css_class = "required" + + def __init__(self, *args, **kwargs): + if "events" in kwargs: + events = kwargs.pop("events", None) + else: + events = [] + for f in args[0]: + if "_" not in f: + if f + "_cat" in args[0]: + events.append( + (Event.objects.get(pk=int(f)), args[0][f + "_cat"]) + ) + super().__init__(*args, **kwargs) + + for e, c in events: + self.fields[str(e.pk)] = BooleanField( + initial=False, + label=_("Apply category {} to the event {}").format(c, e.title), + required=False, + ) + self.fields[str(e.pk) + "_cat"] = CharField(initial=c, widget=HiddenInput()) + + def get_validated(self): + return [ + (e, self.cleaned_data.get(e + "_cat")) + for e in self.fields + if "_" not in e and self.cleaned_data.get(e) + ] diff --git a/src/agenda_culturel/forms.py b/src/agenda_culturel/forms/event.py similarity index 60% rename from src/agenda_culturel/forms.py rename to src/agenda_culturel/forms/event.py index 7cc9574..b3d6cf5 100644 --- a/src/agenda_culturel/forms.py +++ b/src/agenda_culturel/forms/event.py @@ -1,285 +1,43 @@ -import logging import os from string import ascii_uppercase as auc from django.conf import settings from django.core.files import File from django.forms import ( - BooleanField, CharField, ChoiceField, - EmailField, Form, HiddenInput, - ModelChoiceField, ModelForm, MultipleChoiceField, MultipleHiddenInput, RadioSelect, - Textarea, TextInput, - URLField, ValidationError, - FileField, - formset_factory, ) from django.utils.formats import localize from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ -from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget -from .models import ( - CategorisationRule, +from . import ( + GroupFormMixin, + MultipleChoiceFieldAcceptAll, + DynamicArrayWidgetTags, + DynamicArrayWidgetURLs, + SimpleContactForm, +) +from ..models import ( Category, Event, - Message, - Place, - RecurrentImport, Tag, - UserProfile, - SpecialPeriod, ) -from .templatetags.event_extra import event_field_verbose_name, field_to_html -from .templatetags.utils_extra import int_to_abc - -from .models.constants import ( +from ..models.constants import ( TITLE_ISSUE_DATE_IMPORTATION, TITLE_ISSUE_TIME_IMPORTATION, PUBLICATION_ISSUE, ) - -logger = logging.getLogger(__name__) - - -class GroupFormMixin: - template_name = "agenda_culturel/forms/div_group.html" - - class FieldGroup: - def __init__( - self, - id, - label, - display_label=False, - maskable=False, - default_masked=True, - ): - self.id = id - self.label = label - self.display_label = display_label - self.maskable = maskable - self.default_masked = default_masked - - def toggle_field_name(self): - return "group_" + self.id - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.groups = [] - - def add_group(self, *args, **kwargs): - self.groups.append(GroupFormMixin.FieldGroup(*args, **kwargs)) - if self.groups[-1].maskable: - self.fields[self.groups[-1].toggle_field_name()] = BooleanField( - required=False - ) - self.fields[self.groups[-1].toggle_field_name()].toggle_group = True - - def get_fields_in_group(self, g): - return [ - f - for f in self.visible_fields() - if not hasattr(f.field, "toggle_group") - and hasattr(f.field, "group_id") - and f.field.group_id == g.id - ] - - def get_no_group_fields(self): - return [ - f - for f in self.visible_fields() - if not hasattr(f.field, "toggle_group") - and (not hasattr(f.field, "group_id") or f.field.group_id is None) - ] - - def fields_by_group(self): - return [(g, self.get_fields_in_group(g)) for g in self.groups] + [ - ( - GroupFormMixin.FieldGroup("other", _("Other")), - self.get_no_group_fields(), - ) - ] - - def clean(self): - result = super().clean() - - if result: - data = dict(self.data) - # for each masked group, we remove data - for g in self.groups: - if g.maskable and g.toggle_field_name() not in data: - fields = self.get_fields_in_group(g) - for f in fields: - self.cleaned_data[f.name] = None - - return result - - -class TagForm(ModelForm): - required_css_class = "required" - - class Meta: - model = Tag - fields = [ - "name", - "description", - "message", - "in_included_suggestions", - "in_excluded_suggestions", - "principal", - ] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if "name" in kwargs["initial"]: - self.fields["name"].widget = HiddenInput() - - -class TagRenameForm(Form): - required_css_class = "required" - - name = CharField(label=_("Name of new tag"), required=True) - - force = BooleanField( - label=_( - "Force renaming despite the existence of events already using the chosen tag." - ), - ) - - def __init__(self, *args, **kwargs): - force = kwargs.pop("force", False) - name = kwargs.pop("name", None) - super().__init__(*args, **kwargs) - if not (force or (not len(args) == 0 and "force" in args[0])): - del self.fields["force"] - if name is not None and self.fields["name"].initial is None: - self.fields["name"].initial = name - - def is_force(self): - return "force" in self.fields and self.cleaned_data["force"] is True - - -class SimpleContactForm(GroupFormMixin, Form): - email = EmailField( - label=_("Your email"), - help_text=_("Your email address"), - max_length=254, - required=False, - ) - - comments = CharField( - label=_("Comments"), - help_text=_( - "Your message for the moderation team (comments, clarifications, requests...)" - ), - widget=Textarea, - max_length=2048, - required=False, - ) - - def __init__(self, *args, **kwargs): - is_authenticated = "is_authenticated" in kwargs and kwargs["is_authenticated"] - super().__init__(*args, **kwargs) - - if not is_authenticated: - self.add_group( - "communication", - _( - "Receive notification of publication or leave a message for moderation" - ), - maskable=True, - default_masked=True, - ) - self.fields["email"].group_id = "communication" - self.fields["comments"].group_id = "communication" - else: - del self.fields["email"] - del self.fields["comments"] - - -class URLSubmissionSimpleForm(Form): - url = URLField(max_length=512) - - -class URLSubmissionForm(GroupFormMixin, Form): - required_css_class = "required" - - url = URLField(max_length=512) - category = ModelChoiceField( - label=_("Category"), - queryset=Category.objects.all().order_by("name"), - initial=None, - required=False, - ) - tags = MultipleChoiceField( - label=_("Tags"), initial=None, choices=[], required=False - ) - - def __init__(self, *args, **kwargs): - kwargs.pop("is_authenticated", False) - super().__init__(*args, **kwargs) - self.fields["tags"].choices = Tag.get_tag_groups(all=True) - - self.add_group("event", _("Event")) - self.fields["url"].group_id = "event" - self.fields["category"].group_id = "event" - self.fields["tags"].group_id = "event" - - -class URLSubmissionFormWithContact(SimpleContactForm, URLSubmissionForm): - pass - - -URLSubmissionFormSet = formset_factory(URLSubmissionForm, extra=9, min_num=1) - - -class DynamicArrayWidgetURLs(DynamicArrayWidget): - template_name = "agenda_culturel/widgets/widget-urls.html" - - -class DynamicArrayWidgetTags(DynamicArrayWidget): - template_name = "agenda_culturel/widgets/widget-tags.html" - - -class RecurrentImportForm(ModelForm): - required_css_class = "required" - - defaultTags = MultipleChoiceField( - label=_("Tags"), initial=None, choices=[], required=False - ) - - class Meta: - model = RecurrentImport - fields = "__all__" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["defaultTags"].choices = Tag.get_tag_groups(all=True) - - -class CategorisationRuleImportForm(ModelForm): - required_css_class = "required" - - class Meta: - model = CategorisationRule - fields = "__all__" - - -class MultipleChoiceFieldAcceptAll(MultipleChoiceField): - def validate(self, value): - # check if each element is without "/" - for item in value: - if "/" in item: - raise ValidationError(_("The '/' character is not allowed.")) +from ..templatetags.event_extra import event_field_verbose_name, field_to_html +from ..templatetags.utils_extra import int_to_abc class EventForm(GroupFormMixin, ModelForm): @@ -570,131 +328,6 @@ class EventModerateForm(ModelForm): self.cleaned_data["tags"] = list(set(self.cleaned_data["tags"])) -class BatchImportationForm(Form): - required_css_class = "required" - - data = CharField( - label=_("Data"), - widget=Textarea(attrs={"rows": "10"}), - help_text=_("Supported formats: json, html."), - required=True, - ) - - category = ModelChoiceField( - label=_("Category"), - queryset=Category.objects.all().order_by("name"), - help_text=_("Used only if data is html."), - initial=None, - required=False, - ) - tags = MultipleChoiceField( - label=_("Tags"), - initial=None, - choices=[], - help_text=_("Used only if data is html."), - required=False, - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["tags"].choices = Tag.get_tag_groups(all=True) - - -class FixDuplicates(Form): - required_css_class = "required" - - action = ChoiceField() - - def __init__(self, *args, **kwargs): - edup = kwargs.pop("edup", None) - events = edup.get_duplicated() - len(events) - super().__init__(*args, **kwargs) - - choices = [] - initial = None - for i, e in enumerate(events): - if e.status != Event.STATUS.TRASH or e.modified(): - msg = "" - if e.local_version(): - msg = _(" (locally modified version)") - if e.status != Event.STATUS.TRASH: - initial = "Select-" + str(e.pk) - if e.pure_import(): - msg = _(" (synchronized on import version)") - choices += [ - ( - "Select-" + str(e.pk), - _("Select {} as representative version.").format(auc[i] + msg), - ) - ] - - for i, e in enumerate(events): - if e.status != Event.STATUS.TRASH and e.local_version(): - choices += [ - ( - "Update-" + str(e.pk), - _( - "Update {} using some fields from other versions (interactive mode)." - ).format(auc[i]), - ) - ] - - extra = "" - if edup.has_local_version(): - extra = _(" Warning: a version is already locally modified.") - - if initial is None: - initial = "Merge" - choices += [ - ( - "Merge", - _("Create a new version by merging (interactive mode).") + extra, - ) - ] - for i, e in enumerate(events): - if e.status != Event.STATUS.TRASH: - choices += [ - ( - "Remove-" + str(e.pk), - _("Make {} independent.").format(auc[i]), - ) - ] - choices += [("NotDuplicates", _("Make all versions independent."))] - - self.fields["action"].choices = choices - self.fields["action"].initial = initial - - def is_action_no_duplicates(self): - return self.cleaned_data["action"] == "NotDuplicates" - - def is_action_select(self): - return self.cleaned_data["action"].startswith("Select") - - def is_action_update(self): - return self.cleaned_data["action"].startswith("Update") - - def is_action_remove(self): - return self.cleaned_data["action"].startswith("Remove") - - def get_selected_event_code(self): - if ( - self.is_action_select() - or self.is_action_remove() - or self.is_action_update() - ): - return int(self.cleaned_data["action"].split("-")[-1]) - else: - return None - - def get_selected_event(self, edup): - selected = self.get_selected_event_code() - for e in edup.get_duplicated(): - if e.pk == selected: - return e - return None - - class SelectEventInList(Form): required_css_class = "required" @@ -888,190 +521,96 @@ class MergeDuplicates(Form): return None -class CategorisationForm(Form): +class FixDuplicates(Form): required_css_class = "required" + action = ChoiceField() + def __init__(self, *args, **kwargs): - if "events" in kwargs: - events = kwargs.pop("events", None) - else: - events = [] - for f in args[0]: - if "_" not in f: - if f + "_cat" in args[0]: - events.append( - (Event.objects.get(pk=int(f)), args[0][f + "_cat"]) - ) + edup = kwargs.pop("edup", None) + events = edup.get_duplicated() + len(events) super().__init__(*args, **kwargs) - for e, c in events: - self.fields[str(e.pk)] = BooleanField( - initial=False, - label=_("Apply category {} to the event {}").format(c, e.title), - required=False, + choices = [] + initial = None + for i, e in enumerate(events): + if e.status != Event.STATUS.TRASH or e.modified(): + msg = "" + if e.local_version(): + msg = _(" (locally modified version)") + if e.status != Event.STATUS.TRASH: + initial = "Select-" + str(e.pk) + if e.pure_import(): + msg = _(" (synchronized on import version)") + choices += [ + ( + "Select-" + str(e.pk), + _("Select {} as representative version.").format(auc[i] + msg), + ) + ] + + for i, e in enumerate(events): + if e.status != Event.STATUS.TRASH and e.local_version(): + choices += [ + ( + "Update-" + str(e.pk), + _( + "Update {} using some fields from other versions (interactive mode)." + ).format(auc[i]), + ) + ] + + extra = "" + if edup.has_local_version(): + extra = _(" Warning: a version is already locally modified.") + + if initial is None: + initial = "Merge" + choices += [ + ( + "Merge", + _("Create a new version by merging (interactive mode).") + extra, ) - self.fields[str(e.pk) + "_cat"] = CharField(initial=c, widget=HiddenInput()) - - def get_validated(self): - return [ - (e, self.cleaned_data.get(e + "_cat")) - for e in self.fields - if "_" not in e and self.cleaned_data.get(e) ] + for i, e in enumerate(events): + if e.status != Event.STATUS.TRASH: + choices += [ + ( + "Remove-" + str(e.pk), + _("Make {} independent.").format(auc[i]), + ) + ] + choices += [("NotDuplicates", _("Make all versions independent."))] + self.fields["action"].choices = choices + self.fields["action"].initial = initial -class EventAddPlaceForm(Form): - required_css_class = "required" + def is_action_no_duplicates(self): + return self.cleaned_data["action"] == "NotDuplicates" - place = ModelChoiceField( - label=_("Place"), - queryset=Place.objects.all().order_by("name"), - empty_label=_("Create a missing place"), - required=False, - ) - add_alias = BooleanField(initial=True, required=False) + def is_action_select(self): + return self.cleaned_data["action"].startswith("Select") - def __init__(self, *args, **kwargs): - self.instance = kwargs.pop("instance", False) - super().__init__(*args, **kwargs) - if self.instance.location: - self.fields["add_alias"].label = _( - 'Add "{}" to the aliases of the place' - ).format(self.instance.location) + def is_action_update(self): + return self.cleaned_data["action"].startswith("Update") + + def is_action_remove(self): + return self.cleaned_data["action"].startswith("Remove") + + def get_selected_event_code(self): + if ( + self.is_action_select() + or self.is_action_remove() + or self.is_action_update() + ): + return int(self.cleaned_data["action"].split("-")[-1]) else: - self.fields.pop("add_alias") - if self.instance.exact_location: - self.fields["place"].initial = self.instance.exact_location - self.fields["add_alias"].initial = False + return None - def modified_event(self): - return self.cleaned_data.get("place") - - def save(self): - if self.cleaned_data.get("place"): - place = self.cleaned_data.get("place") - self.instance.exact_location = place - self.instance.save(update_fields=["exact_location"]) - if self.cleaned_data.get("add_alias"): - if place.aliases: - place.aliases.append(self.instance.location.strip()) - else: - place.aliases = [self.instance.location.strip()] - place.save() - - return self.instance - - -class PlaceForm(GroupFormMixin, ModelForm): - required_css_class = "required" - - apply_to_all = BooleanField( - initial=True, - label=_( - "On saving, use aliases to detect all matching events with missing place" - ), - required=False, - ) - - class Meta: - model = Place - fields = "__all__" - widgets = {"location": TextInput()} - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.add_group("header", _("Header")) - self.fields["name"].group_id = "header" - - self.add_group("address", _("Address")) - self.fields["address"].group_id = "address" - self.fields["postcode"].group_id = "address" - self.fields["city"].group_id = "address" - self.fields["location"].group_id = "address" - - self.add_group("meta", _("Meta")) - self.fields["aliases"].group_id = "meta" - - self.add_group("information", _("Information")) - self.fields["description"].group_id = "information" - - def as_grid(self): - result = ( - '