Skip to main content

Additional Functions for Django

Project description

Additional code for Django

CI Image Test Coverage Maintainability

What this?

This repository contains additional code for Django.

Why I create this?

Because I love Django, and usually using it. However, I found some essential code was lacked for modern web development. For example, you might want to send Ajax Payload like this:

{
  "name": "John Doe",
  "age": 49,
  "email": "john@example.com",
  "email_aliases": [
    "john.due@example.com",
    "due_49@example.com",
    "john.1968@example.com"
  ]
}

In this case, you can validate name, age, and email field by using Form layer on Django. However, email_aliases cannot be validated because it's a list and it should validate each value whether it is email-formatted or not.

To support this case (and some other cases that Django can't handle), I wrote some code to support List validation.

How To Use It

Forms

Angular form

As you can see above sections, you'll need to implement redundant code:

from django import forms
from .models import UserInfo

class UserInfoForm(forms.ModelForm):
  class Meta(object):
    model = UserInfo
    exclude = ("2fa_secret", )
    # They are already implemented because UserInfoForm inherit ModelForm
    # and the target model has the fields.
    widgets = {
      "age": forms.NumberInput(attrs={"data-ng-model": "model.age"}),
      "phone": forms.TextInput(attrs={"data-ng-model": "model.phone"}),
      "street": forms.TextInput(attrs={"data-ng-model": "model.street"}),
      "city": forms.TextInput(attrs={"data-ng-model": "model.city"}),
      "state": forms.TextInput(attrs={"data-ng-model": "model.state"})
    }

However, you can implement simpler code by using AngularForm:

from django import forms
from djextra.forms.angular1 import AngularForm

class UserInfoForm(AngularForm, forms.ModelForm):
  ng_model_prefix = "model" # Change this if you want to use other than "model"
  class Meta(object):
    model = UserInfo
    exclude = ("2fa_secret", )
    # Automatically generates AngularJS forms.
Data binding between AngularJS and Django

If you want put the value to scope model on initialization, you might have 2 ways:

  1. Serialize your model into json by using json.dumps and django.forms.model_to_dict
  2. Set handle_ng_init meta attribute

The first one is very clear, convert your model into dict with django.forms.model_to_dict, and serialize the dict into JSON, and finally put the text as data-ng-init to the form like this:

<form data-ng-init="model = {{ view.model_dict | tojson }}">
  <!-- bla bla bla bla... -->
</form>

The second one is simple; just set handle_ng_init Meta attribute of the form to True like this:

from django import forms
from djextra.forms.angular1 import AngularForm

class UserInfoForm(AngularForm, forms.ModelForm):
  ng_model_prefix = "model" # Change this if you want to use other than "model"
  handle_ng_init = True
  class Meta(object):
    model = UserInfo
    exclude = ("2fa_secret", )
    # Automatically generates AngularJS forms.

If you want to specify what value to be set, you can use ng_init_format_func meta attribute like this:

from django import forms
from djextra.forms.angular1 import AngularForm

class UserInfoForm(AngularForm, forms.ModelForm):
  ng_model_prefix = "model" # Change this if you want to use other than "model"
  handle_ng_init = True
  ng_init_format_func = {
    "age": lambda value: f"{value} years old"
  }
  class Meta(object):
    model = UserInfo
    exclude = ("2fa_secret", )
    # Automatically generates AngularJS forms.

However, as you know, server-side is quite different from client side, so to keep that age is formatted, you might also need to write client-side code.

All required forms

If you'd like to make all fields required on ModelForm, you will re-implement entire fields like this:

from django import forms
from .models import UserInfo

class UserInfoForm(forms.ModelForm):
  class Meta(object):
    model = UserInfo
    exclude = ("2fa_secret", )

  # Assume that all fields are optional.
  age = forms.IntegerField(
    required=True,
    widget=forms.NumberInput(attrs={"data-ng-model": "model.age"})
  )
  phone = forms.CharField(
    required=True,
    widget=forms.TextInput(attrs={"data-ng-model": "model.phone"})
  )
  street = forms.CharField(
    required=True,
    widget=forms.TextInput(attrs={"data-ng-model": "model.street"})
  )
  city = forms.CharField(
    required=True,
    widget=forms.TextInput(attrs={"data-ng-model": "model.city"})
  )
  state = forms.CharField(
    required=True,
    widget=forms.TextInput(attrs={"data-ng-model": "model.state"})
  )

Moreover, you will not be able to check if the field is proper unless you refer Django's code. To reduce this time consumption, I implemented AllReqiuredForm:

from django import forms
from djextra.forms.angular1 import AllRequiredForm
from .models import UserInfo

class UserInfoForm(AllRequiredForm, forms.ModelForm):
  class Meta(object):
    model = UserInfo
    exclude = ("2fa_secret", )
    # Assume that all fields are optional.

By using AllRequiredForm, you can reduce your LOC like above. Of course, you can put optional field as exceptions like this:

from django import forms
from djextra.forms.angular1 import AllRequiredForm
from .models import UserInfo

class UserInfoForm(AllRequiredForm, forms.ModelForm):
  class Meta(object):
    model = UserInfo
    exclude = ("2fa_secret", )
    # Assume that all fields are optional.
    # By specifying optional, the specified fields won't
    # become a required field.
    optional = ("phone", )

FieldAttributeForm

When you set attribute, especially with ModelForm, you might need to re-set widget with widget Meta attribute like this:

from django.db import models as db
from django import forms


class NamePrice(db.Model):
  name = db.CharField()
  price = db.IntegerField()


class NameDescForm(forms.ModelForm):
  class Meta(object):
    model = NamePrice
    exclude = ("id", )
    widgets = {
      "price": forms.NumberInput(attrs={"max": "100"})
    }

This is okay when you know what widget is used and attribute max is the fixed value. However, if you don't know what widget is used, or max is the dynamic value by the server, Django might not have suitable solution. To solve this problem, djextra has a form named FieldAttributeForm and you can use it like this:

from django.db import models as db
from django import forms

from django.conf import settings


class NamePrice(db.Model):
  name = db.CharField()
  price = db.IntegerField()


class NameDescForm(FieldAttributeForm, forms.ModelForm):
  class Meta(object):
    model = NamePrice
    exclude = ("id", )
    fld_attrs = {
        "price": {
            # The point is the attribute can be callable.
            "max": lambda form, fld, name, value: 100 if value else "",
            "min": "0"
        },
    }

In addition to this, FieldAttributeForm can set attributes that can be applied to all the fields by using common_attrs meta attribute:

from django.db import models as db
from django import forms

from django.conf import settings


class NamePrice(db.Model):
  name = db.CharField()
  price = db.IntegerField()


class NameDescForm(FieldAttributeForm, forms.ModelForm):
  class Meta(object):
    model = NamePrice
    exclude = ("id", )
    common_attrs = {
      # Also it can be callable.
      "data-on-delay": lambda form, fld, name, value: (
        f"delay('{name}',{value})"
      ),
      "data-on-load": "test()",
    }
    fld_attrs = {
        "price": {
            "max": lambda form, fld, name, value: 100 if value else "",
            "min": 0
        },
    }

Form Fields

ListField

ListField is used to handle a list of values like above example. To use ListField, you can write a form like this:

forms.py

from django import forms
from djextra import forms as exforms


class ExampleForm(forms.Form):
  name = forms.CharField()
  age = forms.IntegerField()
  email = forms.EmailField()
  email_aliases = exforms.ListField(field=forms.EmailField())

Then, Inputting the data as usual, the validation will start. If you don't specify field keyword argument, django.forms.CharField object is specified.

Widgets

Widgets for Angular Materials

If you like Material Design, you'd also like to use Angular Material, but as you can see the doc. the components are using special tags. For example, select and option input controllers should be replaced with mdSelect and mdOption and they are not provided by built-in widgets.

This widget provides the widgets:

from django import forms
from djextra.forms.angular1 import (
  AngularForm, MDSelect, MDMultiSelect, MDDatePicker, MDDateSelect, MDCheckBox
)

from .models import ExampleModel

class ExampleForm(AngularForm, forms.ModelForm):
  class Meta(object):
    model = ExampleModel
    exclude = ("secret_field", )
    widgets = {
      "start_since": MDDateSelect(),
      "available_date": MDDatePicker(),
      "shape": MDSelect(choices=(
        ("F", "Fat"), ("N": "Normal"), ("T", "Thin")
      )),
      "needs_fill": MDCheckBox("Fill with border color?")
    }

Contribution

Contribution of code is welcome, and the code is tested with tox. Before sending your pull request, please check you tested your code very well.

License

This repository is licensed under the terms of MIT License. Please check LICENSE.md for the detail.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

djextra-1.1.8.tar.gz (11.9 kB view details)

Uploaded Source

File details

Details for the file djextra-1.1.8.tar.gz.

File metadata

  • Download URL: djextra-1.1.8.tar.gz
  • Upload date:
  • Size: 11.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.5.0.1 requests/2.21.0 setuptools/40.6.2 requests-toolbelt/0.8.0 tqdm/4.29.1 CPython/3.7.2

File hashes

Hashes for djextra-1.1.8.tar.gz
Algorithm Hash digest
SHA256 4251d690109bed3e712e2f66e4e89320a265b6ac9d653e6eb5b73c6e88854752
MD5 cb376ddfc6530206d01a29342488b37a
BLAKE2b-256 5fc265104ea9a0baabba7d448965b87b2224642d0994943c4bbc0a6b9bcc8ae8

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page