FieldBuilder Snippet

ModelForm at full power

Marco Alabruzzo

Full stack developer @ Growth Street

Marco Alabruzzo

http://marcoala.com - marco.alabruzzo@gmail.com

London Django Meetup Group

13 December 2016

 I like my code like I like my whisky, DRY

John - developer

A user interface is like a joke.

If you have to explain it, it’s not that good

Bob - product manager

ModelForm

I

# my_app.models.py
from django.db import models
from django.core import validators



class UserProfile(models.Model):
    name = models.CharField()
    email = models.EmailField()
    password = models.CharField(max_length=50)
    company_name = models.CharField()
    company_number = models.CharField(
        max_length=10,
        validators=[
            validators.RegexValidator(
                regex='^(SC|)[0-9]{8}$',
                message='This is not a correct UK company number'
            ),
        ]
# my_app.forms.py
from django import forms
from my_app.models import UserProfile



class UserProfileForm(models.ModelForm):
    class Meta:
        model = UserProfile
        fields = ['name', 'email', 'password', 'company_name', 'company_number']
        widgets = {
            'password': forms.PasswordInput(),
        }

We want to add a new field, telephone number. It must be validated as with a max length of 11 characters​.

Of course, the new field is required for new users, but we don’t have this data for the old ones,
so admins should be able to leave the field blank 

Bob - product manager

# my_app.models.py
from django.db import models
from django.core import validators



class UserProfile(models.Model):
    name = models.CharField()
    email = models.EmailField()
    password = models.CharField(max_length=50)
    company_name = models.CharField()
    company_number = models.CharField(
        max_length=10,
        validators=[
            validators.RegexValidator(
                regex='^(SC|)[0-9]{8}$',
                message='This is not a correct UK company number'
            ),
        ]
    phone_number = models.CharField(max_length=11, null=True, blank=True)
# my_app.forms.py
from django import forms
from my_app.models import UserProfile



class AdminUserProfileForm(models.ModelForm):
    class Meta:
        model = UserProfile
        fields = ['name', 'email', 'password', 'company_name', 'company_number', 'phone_number']
        widgets = {
            'password': forms.PasswordInput(),
        }



class UserProfileForm(AdminUserProfileForm):
    phone_number = forms.CharField(max_length=11, required=True)

The code is not 100% DRY, but is still pretty good

John - developer

More and more of our customer are asking to have more than one user for the same company

While our designers work on the new feature can you prepare the database to accommodate it?

Bob - product manager

# my_app.models.py
from django.db import models
from django.core import validators



class UserProfile(models.Model):
    name = models.CharField()
    email = models.EmailField()
    password = models.CharField(max_length=50)
    company = models.ForeignKey(CompanyProfile)


class CompanyProfile(models.Model):
    company_name = models.CharField()
    company_number = models.CharField(
        max_length=10,
        validators=[
            validators.RegexValidator(
                regex='^(SC|)[0-9]{8}$',
                message='This is not a correct UK company number'
            ),
        ]
    phone_number = models.CharField(max_length=11, null=True, blank=True)
# my_app.forms.py
from django import forms


class AdminUserProfileForm(forms.Form):
    name = forms.CharField()
    email = forms.EmailField()
    password = forms.CharField(max_length=50)
    company_name = forms.CharField()
    company_number = forms.CharField(
        max_length=10,
        validators=[
            validators.RegexValidator(
                regex='^(SC|)[0-9]{8}$',
                message='This is not a correct UK company number'
            ),
        ]
    phone_number = forms.CharField(max_length=11, required=False)
    
    def save(self):
        # define save logic here


class UserProfileForm(forms.Form):
    phone_number = forms.CharField(max_length=11, required=True)

There is no way I can make this DRY

John - sad developer

I have to replicate ALL the field definitions between the Model and the Form

Maybe I can just become product manager...

# my_app.forms.py
from django import forms
from field_builder.forms import FieldBuilder
from my_app.models import UserProfile, CompanyProfile


class AdminUserProfileForm(models.Form):
    name = FieldBuilder(UserProfile, 'name')
    email = FieldBuilder(UserProfile, 'email')
    password = FieldBuilder(UserProfile, 'password', widget=forms.PasswordInput())
    company_name = FieldBuilder(CompanyProfile, 'company_name')
    company_number = FieldBuilder(CompanyProfile, 'company_number')
    phone_number = FieldBuilder(CompanyProfile, 'company_number')


    def save(self):
        # you have to define your own save method now
        # this is Form not a ModelForm


class UserProfileForm(AdminUserProfileForm):
    phone_number = FieldBuilder(CompanyProfile, 'company_number', required=True)

What if we could be a ModelForm field by field?

# field_builder/forms.py

class FieldBuilder(object):
    def __new__(cls, model, field_name, **kwargs):
        model_field = model._meta.get_field(field_name)
        return model_field.formfield(**kwargs)

FieldBuilder

exposes the internal functionalities of the ModelForm, allowing you to generate a form field based on a model field

and allow you to override a single field parameter

Thank you!

(Questions?)

FieldBuilder Snippet

By Marco Alabruzzo

FieldBuilder Snippet

  • 1,114