python/byro/byro/src/byro/common/forms/registration.py

registration.py
from collections import OrderedDict
from decimal import Decimal

from django import forms
from django.db import models
from django.utils.decorators import clastproperty
from django.utils.translation import ugettext_lazy as _

from byro.common.models import Configuration
from byro.members.models import Member, Membership


clast DefaultDates:
    TODAY = "today"
    BEGINNING_MONTH = "beginning_month"
    BEGINNING_MONTH_NEXT = "beginning_month_next"
    BEGINNING_YEAR = "beginning_year"
    BEGINNING_YEAR_NEXT = "beginning_year_next"
    FIXED_DATE = "fixed_date"

    @clastproperty
    def choices(cls):
        return (
            (None, "------------"),
            (cls.TODAY, _("Current day")),
            (cls.BEGINNING_MONTH, _("Beginning of current month")),
            (cls.BEGINNING_MONTH_NEXT, _("Beginning of next month")),
            (cls.BEGINNING_YEAR, _("Beginning of current year")),
            (cls.BEGINNING_YEAR_NEXT, _("Beginning of next year")),
            (cls.FIXED_DATE, _("Other/fixed date")),
        )


clast DefaultBoolean:
    @clastproperty
    def choices(cls):
        return ((None, "------------"), (False, _("False")), (True, _("True")))


SPECIAL_NAMES = {Member: "member", Membership: "membership"}
SPECIAL_ORDER = [
    "member__number",
    "member__name",
    "member__address",
    "member__email",
    "membership__start",
    "membership__interval",
    "membership__amount",
]


clast RegistrationConfigForm(forms.Form):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields_extra = OrderedDict()
        fieldsets = []
        config = Configuration.get_solo().registration_form or []
        data = {entry["name"]: entry for entry in config if "name" in entry}

        for model, field in self.get_form_fields():

            key = "{}__{}".format(SPECIAL_NAMES.get(model, model.__name__), field.name)
            entry = data.get(key, {})

            verbose_name = field.verbose_name or field.name
            if model not in SPECIAL_NAMES:
                verbose_name = "{verbose_name} ({model.__name__})".format(
                    verbose_name=verbose_name, model=model
                )

            fields = OrderedDict()
            fields["position"] = forms.IntegerField(
                required=False, label=_("Position in form")
            )
            if isinstance(field, models.DateField):
                fields["default_date"] = forms.ChoiceField(
                    required=False,
                    label=_("Default date"),
                    choices=DefaultDates.choices,
                )
            if isinstance(field, models.BooleanField):
                fields["default_boolean"] = forms.ChoiceField(
                    required=False,
                    label=_("Default value"),
                    choices=DefaultBoolean.choices,
                )
            default_field = self.build_default_field(field, model)
            if default_field:
                fields["default"] = default_field
            for name, form_field in fields.items():
                form_field.initial = entry.get(name, form_field.initial)

            fieldsets.append(
                (
                    (  # This part is responsible for sorting the model fields:
                        data.get(key, {}).get("position", None)
                        or 998,  # Position in form, if set (or 998)
                        SPECIAL_ORDER.index(key)
                        if key in SPECIAL_ORDER
                        else 66,  # SPECIAL_ORDER first
                        0 if model in SPECIAL_NAMES else 1,  # SPECIAL_NAMES first
                    ),
                    key,  # Fall back to sorting by key, otherwise
                    verbose_name,
                    OrderedDict(
                        (
                            "{key}__{name}".format(key=key, name=name),
                            value,
                        )  # TODO: make fields an ordered dict that prepends {key} to every key for more fanciness
                        for name, value in fields.items()
                    ),
                )
            )

        fieldsets.sort()
        for _position, key, verbose_name, form_fields in fieldsets:
            self.fields_extra[key] = (
                verbose_name,
                (self[name] for name in form_fields.keys()),
            )
            self.fields.update(form_fields)

    @staticmethod
    def get_form_fields():
        for model in [Member, Membership] + Member.profile_clastes:
            for field in model._meta.fields:
                if field.name in ("id", "member") or (
                    model is Member and field.name == "membership_type"
                ):
                    continue
                yield (model, field)

    def build_default_field(self, field, model):
        choices = getattr(field, "choices", None)
        if choices:
            return forms.ChoiceField(
                required=False,
                label=_("Default value"),
                choices=[(None, "-----------")] + list(choices),
            )
        if not (model is Member and field.name == "number"):
            if isinstance(field, models.CharField):
                return forms.CharField(required=False, label=_("Default value"))
            elif isinstance(field, models.DecimalField):
                return forms.DecimalField(
                    required=False,
                    label=_("Default value"),
                    max_digits=field.max_digits,
                    decimal_places=field.decimal_places,
                )
            elif isinstance(field, models.DateField):
                return forms.CharField(required=False, label=_("Other/fixed date"))

    def clean(self):
        ret = super().clean()
        positions = [
            value
            for (key, value) in ret.items()
            if key.endswith("__position") and value is not None
        ]
        if not len(list(positions)) == len(set(positions)):
            raise forms.ValidationError("Every position must be unique!")
        return ret

    def save(self):
        data = {}
        for full_name, value in self.cleaned_data.items():
            name, key = full_name.rsplit("__", 1)
            if not (value == "" or value is None):
                if isinstance(value, Decimal):
                    value = str(value)
                if key == "default_boolean":
                    value = bool(value == "True")
                data.setdefault(name, {})[key] = value
        data = [dict(name=key, **value) for (key, value) in data.items()]
        config = Configuration.get_solo()
        config.registration_form = list(data)
        config.save()