ejplatform/ej-server

View on GitHub
src/ej/forms.py

Summary

Maintainability
A
1 hr
Test Coverage
from operator import attrgetter

from django.forms import Form, ModelForm, widgets
from django.utils.translation import gettext_lazy as _
from hyperpython import div, input_

NOT_GIVEN = object()


# TODO: move this functionality to Django-Boogie
class EjForm(Form):
    """
    Form with additional functionality.
    """

    def __init__(self, data=None, files=None, *args, request=None, **kwargs):
        if request is not None and request.method in self._meta_property("http_methods", ("POST",)):
            method = request.method
            data = getattr(request, method)
            kwargs.setdefault("files", request.FILES)
            super().__init__(data, *args, **kwargs)
            self.http_method = method
        else:
            super().__init__(data, *args, **kwargs)
            self.http_method = getattr(request, "method", None)

    def _meta_property(self, prop, default=NOT_GIVEN):
        try:
            return getattr(getattr(self, "Meta", None), prop)
        except AttributeError:
            if default is NOT_GIVEN:
                raise
            return default

    def is_valid_http(self, method):
        """
        Return true if form is valid and was submitted with the given HTTP
        method.

        Args:
            method (str or sequence): A string describing the method or a list
            of string
        """
        if self.http_method is None:
            msg = "must be initialized with a request to use this function"
            raise RuntimeError(msg)
        if (
            isinstance(method, str)
            and self.http_method == method.upper()
            or self.http_method in map(str.upper, method)
        ):
            return self.is_valid()
        else:
            return False

    is_valid_post = lambda self: self.is_valid_http("POST")
    is_valid_get = lambda self: self.is_valid_http("GET")
    is_valid_put = lambda self: self.is_valid_http("PUT")
    is_valid_patch = lambda self: self.is_valid_http("PATCH")
    is_valid_delete = lambda self: self.is_valid_http("DELETE")

    def set_widget_attributes(self, attribute, value=None, from_attr=None):
        """
        Define the given attribute to all widgets in the form.
        """
        getter = from_attr and attrgetter(from_attr)
        for elem in self:
            if from_attr:
                value = getter(elem)
                elem.field.widget.attrs.setdefault(attribute, value)
            elif value is None:
                elem.field.widget.attrs.pop(attribute, None)
            else:
                elem.field.widget.attrs.setdefault(attribute, value)


class EjUserForm(EjForm):
    def __init__(self, *args, **kwargs):
        if "user" in kwargs:
            user = kwargs.pop("user")
        elif "request" in kwargs:
            user = kwargs["request"].user
        else:
            raise TypeError("User must be provided from request or user parameter")
        super().__init__(*args, **kwargs)
        self.user = user


class EjModelForm(EjForm, ModelForm):
    """
    A ModelForm version of the extended form.
    """

    def save(self, commit=True, **kwargs):
        instance = super().save(commit=False)
        for k, v in kwargs.items():
            setattr(instance, k, v)
        if commit:
            instance.save()
            self._save_m2m()
        return instance


class PlaceholderForm(EjForm):
    """
    Add placeholders from field labels.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_widget_attributes("placeholder", from_attr="label")


class FileInput(widgets.FileInput):
    """
    A custom file input widget used by all forms of ej.
    To specify witch type of file it will accept, pass 'accept'
    to attrs dict.
    E.g.: widgets.FileInput(attrs={'accept':'image/*'})
    """

    class Media:
        js = ("js/file-input.js",)

    def render(self, name, value, attrs=None, renderer=None):
        widget = self.get_context(name, value, attrs)["widget"]

        w_name = widget.get("name", "")
        w_type = widget.get("type", "")
        w_attrs = widget.get("attrs", {})

        return div(class_="FileInput")[
            div(class_="PickFileButton")[
                input_(style="opacity: 0", type_=w_type, name=w_name, **w_attrs), _("Choose a file")
            ],
            div(class_="FileStatus")[_("No file chosen")],
        ].render()