adamghill / django-unicorn

The magical reactive component framework for Django ✨
https://www.django-unicorn.com
MIT License
2.25k stars 112 forks source link

Support validation for fields that are objects #220

Open Franziskhan opened 3 years ago

Franziskhan commented 3 years ago

How do I validate an email field with Django Unicorn? Email field validation works fine with Django vanilla forms (which I was using before replacing them with unicorn ones) and with Django admin, but not at all with Unicorn. Some types of field are validated, like integer field, but not email field. I wasn't using forms in unicorn, but since they are necessary for validation I decided to import my project Model Forms. Is that possible? And how do I validate an email field? Already tried self.validate() and if self.is_valid(), they don't work.

I attach parts of my code to be more clear.

# companyunicorn.py

from django_unicorn.components import QuerySetType, UnicornView
from businessapp.models import Company, CompanyCategory, Contact
from django.contrib.auth.models import User, Group
from datetime import datetime,timedelta
from django.utils import timezone
from django.forms import ModelForm
from django import forms

class CompanyForm(ModelForm):
    class Meta:
        model = Company
        fields = ['__all__' ]

class CompanyunicornView(UnicornView):
    form_class = CompanyForm

    company_id = None

    company : Company = Company.objects.none()
    is_editing = False

    def __init__(self, *args, **kwargs):
        super().__init__(**kwargs)  # calling super is required
        self.company_id = kwargs.get("company_id")

    def edit(self):
        self.is_editing = True

    def save(self):
        self.company.save()
        self.is_editing = False
        self.load_data()

    def cancel(self):

        self.is_editing = False
        self.load_data()

    def mount(self):
        self.load_data()

    def load_data(self):
        self.company = Company.objects.get(pk=self.company_id)
        self.is_editing = False

<!-- companyunicorn.html -->

{% load static %}

<div class="col-12 col-md-7">
    {% if is_editing %}
    <div class="form-group row">
      <div class="col-sm-3 col-form-label">
        <label for="first-name">Company name</label>
      </div>
      <div class="col-sm-9">
        <input
          type="text"
          class="form-control"
          placeholder="Name"
          unicorn:model.defer="company.name"

        />
      </div>
      </div>

        <!-- ...so on many fields I don't post to make it more readable... -->

    <button
    unicorn:click="save"
    type="button"
    class="btn btn-primary waves-effect waves-float waves-light"
    >
      Save
    </button>
    <button
      unicorn:click="cancel"

      type="button"
      class="btn btn-secondary waves-effect waves-float waves-light"
    >
      Cancel
    </button>
    {% else %}

    <h3>{{ company.name }}</h3>
    <p class="card-text">Company name: {{ company.name }}</p>
    <!-- and more fields here, same as above... -->

    <button     unicorn:click="edit" 
           type="button"      class="btn btn-primary waves-effect waves-float waves-light"  >   Edit  </button>

    {% endif %}

  </div>
Franziskhan commented 3 years ago

Sent pull request #221 to test email field

adamghill commented 3 years ago

I took your PR, tweaked it, and added it as an example in https://github.com/adamghill/django-unicorn/commit/d2e48a6e7b1506b8c54c687ec41e09349705066e.

Basically, Unicorn expects the component field to be named the same as the field in the form. If they match, Unicorn will use the form validation specified.

I'm going to close this issue for now, but let me know if that doesn't make sense or if you have other ideas on how to approach this. Personally, I've tried a few different validation frameworks in the past (I've been using pydantic a bunch recently), but using the built-in Django form validation is appealing because it's not an extra dependency.

Franziskhan commented 3 years ago

Thanks for the prompt reply.

I tried the emailfield in your example project and it works fine. But the problem remains in my project. The validation works as long as it is unicorn:model="email", but not when I put unicorn:model="company.email", that is my emailfield in my model (as you can get i need to save data from my form into my database).

What am I missing? I tried with normal forms too (I am using model forms, as I said), but they don't work too. It seems that it's linked with the unicorn:model line. How can I do that using my model field?

adamghill commented 3 years ago

Ah, sorry! I'm re-opening this issue. What I missed earlier was that you were traversing into the field (e.g. company.email). This is not currently possible, so I need to figure out what a good API might be to enable this. If you have any suggestions for a clean way to represent this use-case in a component, let me know.

adamghill commented 3 years ago

A few thoughts: In addition to supporting form_class on the component view, there could also be a {field}_form_class where "field" would refer to a field on the component. For example:

class BookForm(forms.ModelForm):
    class Meta:
        model = Book

class BookView(UnicornView):
    book_form_class = BookForm

    book: Book = None

Another option would be a form_classes on the component view which would be a dictionary that maps a component field to a form class.

class BookForm(forms.ModelForm):
    class Meta:
        model = Book

class BookView(UnicornView):
    form_classes = {
        "book": BookForm,
    }

    book: Book = None

Or maybe another option I'm not thinking of?

Franziskhan commented 3 years ago

Best solution is the one that need less code and that is more readable. The first solution allows you to type a bit less, while the second one is definetely more readable and less confused.

Can't tell if this is possible since I'm a junior coder, but best solution would be declaring just the model form we are using and then it should take all the fields in it, without typing them one by one, like we do in a vanilla django view.

Pineapple217 commented 1 year ago

I am having the same issue. Adding the code below makes sure u can't write invalid date to your database but it not a great fix.

def save(self, *args, **kwargs):
        self.full_clean()
        return super().save(*args, **kwargs)

Any of the solutions would be great. If this were implemented, components for models could be written very elegantly. Would be greatly appreciated.