Package: djextra
Additional code for DjangoWhat 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 usingFormlayer 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 ItFormsAngular formAs you can see above sections, you'll need to implement redundant code:fromdjangoimportformsfrom.modelsimportUserInfoclassUserInfoForm(forms.ModelForm):classMeta(object):model=UserInfoexclude=("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 usingAngularForm:fromdjangoimportformsfromdjextra.forms.angular1importAngularFormclassUserInfoForm(AngularForm,forms.ModelForm):ng_model_prefix="model"# Change this if you want to use other than "model"classMeta(object):model=UserInfoexclude=("2fa_secret",)# Automatically generates AngularJS forms.Data binding between AngularJS and DjangoIf you want put the value to scope model on initialization, you might have 2 ways:Serialize your model into json by usingjson.dumpsanddjango.forms.model_to_dictSethandle_ng_initmeta attributeThe first one is very clear, convert your model into dict withdjango.forms.model_to_dict, and serialize the dict into JSON, and finally
put the text asdata-ng-initto the form like this:<formdata-ng-init="model = {{ view.model_dict | tojson }}"><!-- bla bla bla bla... --></form>The second one is simple; just sethandle_ng_initMeta attribute of the form toTruelike this:fromdjangoimportformsfromdjextra.forms.angular1importAngularFormclassUserInfoForm(AngularForm,forms.ModelForm):ng_model_prefix="model"# Change this if you want to use other than "model"handle_ng_init=TrueclassMeta(object):model=UserInfoexclude=("2fa_secret",)# Automatically generates AngularJS forms.If you want to specify what value to be set, you can useng_init_format_funcmeta attribute like this:fromdjangoimportformsfromdjextra.forms.angular1importAngularFormclassUserInfoForm(AngularForm,forms.ModelForm):ng_model_prefix="model"# Change this if you want to use other than "model"handle_ng_init=Trueng_init_format_func={"age":lambdavalue:f"{value}years old"}classMeta(object):model=UserInfoexclude=("2fa_secret",)# Automatically generates AngularJS forms.However, as you know, server-side is quite different from client side, soto
keep thatageis formatted, you might also need to write client-side code.All required formsIf you'd like to make all fields required on ModelForm, you will re-implement
entire fields like this:fromdjangoimportformsfrom.modelsimportUserInfoclassUserInfoForm(forms.ModelForm):classMeta(object):model=UserInfoexclude=("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 implementedAllReqiuredForm:fromdjangoimportformsfromdjextra.forms.angular1importAllRequiredFormfrom.modelsimportUserInfoclassUserInfoForm(AllRequiredForm,forms.ModelForm):classMeta(object):model=UserInfoexclude=("2fa_secret",)# Assume that all fields are optional.By usingAllRequiredForm, you can reduce your LOC like above. Of course,
you can put optional field as exceptions like this:fromdjangoimportformsfromdjextra.forms.angular1importAllRequiredFormfrom.modelsimportUserInfoclassUserInfoForm(AllRequiredForm,forms.ModelForm):classMeta(object):model=UserInfoexclude=("2fa_secret",)# Assume that all fields are optional.# By specifying optional, the specified fields won't# become a required field.optional=("phone",)FieldAttributeFormWhen you set attribute, especially withModelForm, you might need to re-set
widget withwidgetMeta attribute like this:fromdjango.dbimportmodelsasdbfromdjangoimportformsclassNamePrice(db.Model):name=db.CharField()price=db.IntegerField()classNameDescForm(forms.ModelForm):classMeta(object):model=NamePriceexclude=("id",)widgets={"price":forms.NumberInput(attrs={"max":"100"})}This is okay when you know what widget is used and attributemaxis the
fixed value. However, if you don't know what widget is used, ormaxis
the dynamic value by the server, Django might not have suitable solution.
To solve this problem, djextra has a form namedFieldAttributeFormand
you can use it like this:fromdjango.dbimportmodelsasdbfromdjangoimportformsfromdjango.confimportsettingsclassNamePrice(db.Model):name=db.CharField()price=db.IntegerField()classNameDescForm(FieldAttributeForm,forms.ModelForm):classMeta(object):model=NamePriceexclude=("id",)fld_attrs={"price":{# The point is the attribute can be callable."max":lambdaform,fld,name,value:100ifvalueelse"","min":"0"},}In addition to this,FieldAttributeFormcan set attributes that can be applied
to all the fields by usingcommon_attrsmeta attribute:fromdjango.dbimportmodelsasdbfromdjangoimportformsfromdjango.confimportsettingsclassNamePrice(db.Model):name=db.CharField()price=db.IntegerField()classNameDescForm(FieldAttributeForm,forms.ModelForm):classMeta(object):model=NamePriceexclude=("id",)common_attrs={# Also it can be callable."data-on-delay":lambdaform,fld,name,value:(f"delay('{name}',{value})"),"data-on-load":"test()",}fld_attrs={"price":{"max":lambdaform,fld,name,value:100ifvalueelse"","min":0},}Form FieldsListFieldListField is used to handle a list of values like above example.
To use ListField, you can write a form like this:forms.pyfromdjangoimportformsfromdjextraimportformsasexformsclassExampleForm(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 specifyfieldkeyword argument,django.forms.CharFieldobject
is specified.WidgetsWidgets for Angular MaterialsIf you likeMaterial Design, you'd also like to useAngular Material, but
as you can see the doc. the components are using special tags. For example,selectandoptioninput controllers should be replaced withmdSelectandmdOptionand they are not provided by built-in widgets.This widget provides the widgets:fromdjangoimportformsfromdjextra.forms.angular1import(AngularForm,MDSelect,MDMultiSelect,MDDatePicker,MDDateSelect,MDCheckBox)from.modelsimportExampleModelclassExampleForm(AngularForm,forms.ModelForm):classMeta(object):model=ExampleModelexclude=("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?")}ContributionContribution of code is welcome, and the code is tested with tox. Before
sending your pull request, please check you tested your code very well.LicenseThis repository is licensed under the terms of MIT License. Please checkLICENSE.mdfor the detail.