Skip to content

Instantly share code, notes, and snippets.

@raiderrobert
Last active February 17, 2018 03:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raiderrobert/da081a219540c4d62ee1a764e3e91f44 to your computer and use it in GitHub Desktop.
Save raiderrobert/da081a219540c4d62ee1a764e3e91f44 to your computer and use it in GitHub Desktop.
DEP Draft - Django Forms API Rewrite
"""
Proposed New Forms API, not currently possible in Django Forms API (as of 2.0)
"""
from django.forms import ModelForm, ModelFormListField
from .models import Author, Book
# Make this work like serailization relationships in Django Rest Framework
# http://www.django-rest-framework.org/api-guide/relations/
# or relationships in WTForms
# http://wtforms-alchemy.readthedocs.io/en/latest/relationships.html#one-to-many-relations
class BookForm(ModelForm):
class Meta:
model = Book
fields = ['name', 'published_date']
class AuthorWithWorksForm(ModelForm):
works = ModelFormListField(BookForm)
class Meta:
model = Author
fields = ['name', 'works']
# Allow for overrides on the form Meta rather than modification on __init__
# follows WTForms http://wtforms-alchemy.readthedocs.io/en/latest/configuration.html
from django.forms.widgets import TextArea
from .validators import validate_in_past
class AuthorOnlyForm(ModelForm):
class Meta:
model = Author
fields = ['name', 'birth_date']
field_args = {
'name': {
'widget': TextArea
},
'birth_date': {
'required': True,
'validators': [validate_in_past]
}
}
# Provide a means for the form to be client-side validation aware
# Since there's no standard, this very naive implementation may not scale
# Here's an implementation of Parsley.js through the new proposed API
# http://parsleyjs.org/doc/index.html#usage-field
from django.utils.timezone import now
class BookForm(ModelForm):
class Meta:
model = Book
fields = ['name', 'published_date']
field_args = {
'name': {
'widget_attrs': {
'data-parsley-minlength':'4'
}
},
'birth_date': {
'required': True,
'validators': [validate_in_past],
'widget_attrs': {
'data-parsley-max': now().astimezone().date().isoformat()
}
}
}
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=255)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=255)
published_date = models.DateField(blank=True, null=True)
authors = models.ManyToManyField(
Author,
on_delete=models.SET_NULL,
related_name='works'
)
def __str__(self):
return self.name
@loic
Copy link

loic commented Feb 17, 2018

Hi @raiderrobert, I don't think this needs a DEP, it looks more like 3 independent tickets to me.

1/ is basically to turn InlineFormset into a form field. I quite like the idea.

2/ is the wontfixed ticket #17924 and related to #20000.

With hindsight I regret not pushing harder for #17924 and settling for #20000. DRF allowing this and being so widely popular certainly leads to people assuming Django forms works the same. Using this feature extensively with DRF I often feel frustrated not having it for forms.

One of the challenge with this ticket will be how to provide a consistent API as Carl mentioned in https://code.djangoproject.com/ticket/20000#comment:14 : widgets, label, help_text and error_messages, all have first level attributes on the Meta object, and deprecating them would be way too disruptive.

The process for a wontfixed ticket is the take it to the mailing list.

3/ I'm guessing widget_attrs is targeting the <input> tag in the generated HTML? Personally I'm not fond of the tight coupling of Python fields and HTML rendering, I prefer to look at form fields as a description of the data at a high level, then templates should have the whole object available to decide how to render the appropriate widget.

'birth_date': {
    'widget_attrs': {
        'data-parsley-max': now().astimezone().date().isoformat()
    }
}

Imo would be better solved with:

'birth_date': {
    'max':  now().astimezone().date()
}
{{ form.birth_date|add_attrs('data-parsley-max', form.birth_date.max.isoformat()) }}

Of course that's much harder to do with DTL than Jinja2, and currently form.birth_date is a BoundField rather than the actual field, but you get the idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment