diff --git a/django_unicorn/components/mixins.py b/django_unicorn/components/mixins.py index 63572063..c9a762b2 100644 --- a/django_unicorn/components/mixins.py +++ b/django_unicorn/components/mixins.py @@ -1,3 +1,7 @@ +from django import forms +from django.forms import Field +from django.views.generic.edit import FormMixin + from django_unicorn.serializer import model_value @@ -9,3 +13,91 @@ class ModelValueMixin: def value(self, *fields): return model_value(self, *fields) + + +class UnicornFormMixin(FormMixin): + """Mixin for UnicornView to add Forms functionality.""" + + def __init__(self, **kwargs): + if self.success_url is not None: + # FIXME Use AttributeError instead, but AttributeErrors are catched by Unicorn and it overrides it with a generic message + raise AttributeError( + f"You may not use a success URL attribute with Unicorn's {self.__class__.__name__}." + ) + if self.initial: + raise AttributeError( + f"Do not use the 'initial' attr for setting initial data in a component. Set attributes directly in the component class '{self.__class__.__name__}' instead ." + ) + + # set the classes Unicorn attrs dynamically, + for ( + field_name, + field, + ) in self.form_class.base_fields.items(): # type: (str, Field) + # don't override existing "initial" attrs + if not hasattr(self, field_name): + setattr(self.__class__, field_name, "") + + field.widget.attrs.update(self.get_widget_attr(field_name, field)) + + super().__init__(**kwargs) + + def get_widget_attr(self, field_name: str, field: Field) -> Dict[str, Any]: + """Returns an html attribute for the given field. + + This method can be overridden for setting special attributes to some fields. As default, it returns + "unicorn:model""" + + if isinstance( + field, + ( + forms.BooleanField, + forms.NullBooleanField, + forms.RadioSelect, + forms.NullBooleanSelect, + forms.ChoiceField, + forms.ModelChoiceField, + forms.MultipleChoiceField, + forms.ModelMultipleChoiceField, + forms.TypedChoiceField, + forms.TypedMultipleChoiceField, + ), + ): + return {"unicorn:model": field_name} + else: + return {"unicorn:model.lazy": field_name} + + super().__init__(**kwargs) + + def get_initial(self): + return self._attributes() + + def get_success_url(self): + return "" + + def form_valid(self, form): + # TODO is there anything to do here? + # At least a HttpResponseRedirect to success_url is wrong, like in FormMixin + pass + + def form_invalid(self, form): + # TODO is there anything to do here? + pass + + # from django.forms.FormMixin: + # def get_form_kwargs(self): + # """Return the keyword arguments for instantiating the form.""" + # kwargs = super().get_form_kwargs() + # # kwargs = { + # # # "initial": self.get_initial(), + # # # "prefix": self.get_prefix(), + # # } + # + # if self.request.method in ("POST", "PUT"): + # kwargs.update( + # { + # "data": self.request.POST, + # "files": self.request.FILES, + # } + # ) + # return kwargs diff --git a/tests/templates/test_simpleform.html b/tests/templates/test_simpleform.html new file mode 100644 index 00000000..ba33443c --- /dev/null +++ b/tests/templates/test_simpleform.html @@ -0,0 +1,3 @@ +
+ {{ form }} +
\ No newline at end of file diff --git a/tests/urls.py b/tests/urls.py index 02c2f996..48114f4d 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -2,6 +2,7 @@ from django.urls import include, path from django.views.generic import TemplateView +from tests.views.test_unicorn_forms import SimpleFormView def parent_view(request): return render(request, "templates/test_parent_template.html") @@ -13,6 +14,7 @@ def parent_implicit_view(request): urlpatterns = ( path("test", TemplateView.as_view(template_name="templates/test_template.html")), + path("test_forms", SimpleFormView.as_view()), path("test-parent", parent_view, name="test-parent"), path("test-parent-implicit", parent_implicit_view, name="test-parent-implicit"), path( diff --git a/tests/views/test_unicorn_forms.py b/tests/views/test_unicorn_forms.py new file mode 100644 index 00000000..f2b67773 --- /dev/null +++ b/tests/views/test_unicorn_forms.py @@ -0,0 +1,28 @@ +from django.shortcuts import render +from django.template.loader import render_to_string + +from django_unicorn.components import UnicornView +from django_unicorn.components.mixins import UnicornFormMixin +from example.unicorn.forms import ValidationForm + + +class SimpleFormView(UnicornFormMixin, UnicornView): + template_name = "test_simpleform.html" + form_class = ValidationForm + + favourite_color = "red" + + +# noinspection PyUnresolvedReferences +def test_form_fields_attached_as_component_attrs(): + component = SimpleFormView(component_name="test", component_id="12345678") + + assert component.text == "" + assert component.email == "" + + +# def test_form_field_default(client): +# component = SimpleFormView(component_name="test", component_id="12345678") +# +# content = client.get("/test_forms") +# assert content.contains("...")