Package: django-api-forms
Django API FormsDjango Formsapproach in the processing of a RESTful HTTP
request payload (especially for content type likeJSONorMessagePack)
without HTML front-end.MotivationThe main idea was to create a simple and declarative way to specify the format of expecting requests with the ability
to validate them. Firstly, I tried to useDjango Formsto
validate my API requests (I use pure Django in my APIs). I have encountered a problem with nesting my requests without
a huge boilerplate. Also, the whole HTML thing was pretty useless in my RESTful APIs.I wanted to:define my requests as object (Form),pass the request to my defined object (form = Form.create_from_request(request)),validate my requestform.is_valid(),extract dataform.clean_dataproperty.I wanted to keep:friendly declarative Django syntax,
(DeclarativeFieldsMetaclassis beautiful),Validators,ValidationError,Form fields(In the end, I had to "replace" some of them).So I have decided to create a simple Python package to cover all my expectations.Installation# Using pippipinstalldjango-api-forms# Using poetrypeotryadddjango-api-forms# Using setup.pypythonsetup.pyinstallOptional:# msgpack support (for requests with Content-Type: application/x-msgpack)peotryaddmsgpack# ImageField supportpeotryaddPillowInstall application in your Django project by addingdjango_api_formsto yoursINSTALLED_APPS:INSTALLED_APPS=('django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django_api_forms')You can change the default behavior of population strategies or parsers using these settings (listed with default
values). Keep in mind, that dictionaries are not replaced by your settings they are merged with defaults.For more information about the parsers and the population strategies check the documentation.DJANGO_API_FORMS_POPULATION_STRATEGIES={'django_api_forms.fields.FormFieldList':'django_api_forms.population_strategies.IgnoreStrategy','django_api_forms.fields.FileField':'django_api_forms.population_strategies.IgnoreStrategy','django_api_forms.fields.ImageField':'django_api_forms.population_strategies.IgnoreStrategy','django_api_forms.fields.FormField':'django_api_forms.population_strategies.IgnoreStrategy','django.forms.models.ModelMultipleChoiceField':'django_api_forms.population_strategies.IgnoreStrategy','django.forms.models.ModelChoiceField':'django_api_forms.population_strategies.ModelChoiceFieldStrategy'}DJANGO_API_FORMS_DEFAULT_POPULATION_STRATEGY='django_api_forms.population_strategies.BaseStrategy'DJANGO_API_FORMS_PARSERS={'application/json':'json.loads','application/x-msgpack':'msgpack.loads'}ExampleSimple nested JSON request{"title":"Unknown Pleasures","type":"vinyl","artist":{"_name":"Joy Division","genres":["rock","punk"],"members":4},"year":1979,"songs":[{"title":"Disorder","duration":"3:29"},{"title":"Day of the Lords","duration":"4:48","metadata":{"_section":{"type":"ID3v2","offset":0,"byteLength":2048},"header":{"majorVersion":3,"minorRevision":0,"size":2038}}}],"metadata":{"created_at":"2019-10-21T18:57:03+0100","updated_at":"2019-10-21T18:57:03+0100"}}Django API Forms equivalent + validationfromenumimportEnumfromdjango.core.exceptionsimportValidationErrorfromdjango.formsimportfieldsfromdjango_api_formsimportFieldList,FormField,FormFieldList,DictionaryField,EnumField,AnyField,FormclassAlbumType(Enum):CD='cd'VINYL='vinyl'classArtistForm(Form):classMeta:mapping={'_name':'name'}name=fields.CharField(required=True,max_length=100)genres=FieldList(field=fields.CharField(max_length=30))members=fields.IntegerField()classSongForm(Form):title=fields.CharField(required=True,max_length=100)duration=fields.DurationField(required=False)metadata=AnyField(required=False)classAlbumForm(Form):title=fields.CharField(max_length=100)year=fields.IntegerField()artist=FormField(form=ArtistForm)songs=FormFieldList(form=SongForm)type=EnumField(enum=AlbumType,required=True)metadata=DictionaryField(fields.DateTimeField())defclean_year(self):ifself.cleaned_data['year']==1992:raiseValidationError("Year 1992 is forbidden!",'forbidden-value')returnself.cleaned_data['year']defclean(self):if(self.cleaned_data['year']==1998)and(self.cleaned_data['artist']['name']=="Nirvana"):raiseValidationError("Sounds like a bullshit",code='time-traveling')ifnotself._request.user.is_authenticated():raiseValidationError("You can use request in form validation!")returnself.cleaned_data"""Django view example"""defcreate_album(request):form=AlbumForm.create_from_request(request)ifnotform.is_valid():# Process your validation errorprint(form.errors)# Cleaned valid payloadpayload=form.cleaned_dataprint(payload)If you want example with whole Django project, check out repository created bypawldjango_api_forms_modelchoicefield_example, where
he uses library withModelChoiceField.Running Tests# install all dependenciespoetryinstall# run code-style checkpoetryrunflake8.# run the testspoetryrunpythonruntests.pySponsorshipNavicat Premiumis a super awesome database development tool for
cool kids in the neighborhood that allows you to simultaneously connect to MySQL, MariaDB, MongoDB, SQL Server, Oracle,
PostgreSQL, and SQLite databases from a single application. Compatible with cloud databases like Amazon RDS, Amazon
Aurora, Amazon Redshift, Microsoft Azure, Oracle Cloud, Google Cloud and MongoDB Atlas. You can quickly and easily
build, manage and maintain your databases.Especially, I have to recommend their database design tool. Many thanksNavicatfor
supporting Open Source projects 🌈.Made with ❤️ and ☕️ by Jakub Dubec,BACKBONE s.r.o.&contributors.Changelog0.21.0 : 15.12.2021Feature: IntroducedmappingFeature: Override strategies usingfield_type_strategyandfield_strategy0.20.1 : 13.1.2022Fix:DictionaryFieldwas unable to raise validation errors for keys0.20.0 : 14.10.2021Anniversary release 🥳Feature: Population strategies introducedFeature:fillmethod is deprecated and replaced bypopulateFeature:Settingsobject introduced (form.settings)Feature: Pluggable content-type parsers usingDJANGO_API_FORMS_PARSERSsetting0.19.1 : 17.09.2021Typing:mimeargument inFileFieldis supposed to be atuple0.19.0 : 12.07.2021Feature:FieldListandFormFieldListnow supports optional min/max constrains usingmin_length/max_length0.18.0 : 16.04.2021Feature:ModelFormclass introduced (experimental, initial support - not recommended for production)0.17.0 : 24.02.2021Feature:fill_methodintroduced0.16.4 : 20.12.2020Fix: Pillow image object have to be reopened afterImage.verify()call inImageField::to_python0.16.3 : 13.11.2020Fix:ApiFormException('No clean data provided! Try to call is_valid() first.')was incorrectly raised if
request payload was empty duringForm::fillmethod callChange:clean_dataproperty is by defaultNoneinstead of empty dictionary0.16.2 : 06.11.2020Fix: Fixed issue withclean_methods returning values resolved as False (False,None,'')0.16.1 : 29.10.2020Fix: IgnoreModelMultipleChoiceFieldinForm::fill()0.16.0 : 14.09.2020Change: Correctly resolve key postfix ifModelChoiceFieldis used inForm::fill()Change:DjangoApiFormsConfigis createdNote: One more step to get rid ofpytestin project (we don't need it)0.15.1 : 29.08.2020Feature:FileField.content_typeintroduced (contains mime)0.15.0 : 23.08.2020Feature:FileFieldandImageFieldintroducedNote: Defined extras insetup.pyfor optionalPillowandmsgpackdependenciesFeature: WorkingForm::fill()method for primitive data types. IntroducedIgnoreFillMixin0.14.0 : 07.08.2020Feature:BaseForm._requestproperty introduced (now it's possible to use request inclean_methods)0.13.0 : 09.07.2020Fix: FixedContent-Typehandling ifcharsetorboundaryis present0.12.0 : 11.06.2020Fix: Do not call resolvers methods, if property is not required and not present in request0.11.0 : 10.06.2020Change: Non specified non-required fields will no longer be available in the cleaned_data form attribute.0.10.0 : 01.06.2020Change: All package exceptions inherits fromApiFormException.Fix: Specifying encoding while opening files insetup.py(failing on Windows OS).0.9.0 : 11.05.2020Change: Moved field error messages to default_error_messages for easier overriding and testing.Fix: Fix KeyError when invalid values are sent to FieldList.Fix: Removed unnecessary error checking in FieldList.0.8.0 : 05.05.2020Maintenance: Add tests for fieldsChange: Remove DeclarativeFieldsMetaclass and import from Django instead.Change: Msgpack dependency is no longer required.Change: Empty values passed into a FormField now return {} rather than None.Fix: Throw a more user friendly error when passing non-Enums or invalid values to EnumField.0.7.1 : 13.04.2020ChangeUsepoetryinstead ofpipenvChange: Library renamed fromdjango_api_formstodjango-api-forms(cosmetic change without effect)0.7.0 : 03.03.2020Change: Library renamed fromdjango_request_formattertodjango_api_formsChange: Imports in main moduledjango_api_forms0.6.0 : 18.02.2020Feature:BooleanFieldintroduced0.5.8 : 07.01.2020Fix: PassInvalid valueasValidationErrornot as astring0.5.7 : 07.01.2020Fix: Introduced genericInvalid valueerror message, if there isAttributeError,TypeError,ValueError0.5.6 : 01.01.2020Fix: Fixing issue from version0.5.5but this time for realChange: Renamed version file from__version__.pytoversion.py0.5.5 : 01.01.2020Fix: Check instance only if there is a value inFieldListandFormFieldList0.5.4 : 24.12.2019Fix: Added missingmsgpack`` dependency tosetup.py`0.5.3 : 20.12.2019Feature: Introduced genericAnyField0.5.2 : 19.12.2019Fix: Skip processing of theFormFieldif value is not required and empty0.5.1 : 19.12.2019Fix: ProcessEnumFieldeven if it's not marked as required0.5.0 : 16.12.2019Change: Use nativedjango.form.fieldsif possibleChange: Removedkwargspropagation from release0.3.0Change: Changed syntax back todjango.formscompatible (e.g.form.validate_{key}()->form.clean_{key}())Change:FieldListraisesValidationErrorinstead ofRuntimeExceptionif there is a type  in validationChange: Use private properties for internal data in field objectsFixed:FieldListreturns values instead ofNoneFix: Fixed validation inDictionaryFieldMaintenance: Basic unit tests0.4.3 : 29.11.2019Fix: FixedFormhas no attributeself._data0.4.2 : 29.11.2019Fix: If payload is empty, create empty dictionary to avoidNoneTypeerror0.4.1 : 14.11.2019Feature: IntroducedUUIDField0.4.0 : 13.11.2019Feature: IntroducedDictionaryField0.3.0 : 11.11.2019Feature: PropagatekwargsfromForm.is_valid()toForm.validate()andForm.validate_{key}()methods0.2.1 : 4.11.2019Fix: Fixedto_python()in FormFieldList0.2.0 : 31.10.2019Change:Form.validate()replaced byForm.is_valid()Feature:Form.validate()is now used as a last step of form validation and it's aimed to be overwritten if
neededNote: Unit tests initialization0.1.6 : 24.10.2019Fix: Non-required EnumField is now workingFeature: WIP: Initial method for filling objectsForm::fill()0.1.5 : 23.10.2019Fix: Assign errors to form before raisingValidationError0.1.4 : 23.10.2019Fix: Do not return empty error records inForm:errors0.1.3 : 23.10.2019Fix: Use customDeclarativeFieldsMetaclassbecause of customFieldclassFix: Do not return untouched fields inForm::payloadFix: Fix for Nonedefault_validatorsinField0.1.2 : 22:10.2019Feature: Support forvalidation_{field}methods inForm(initial support)0.1.1 : 22.10.2019Feature:EnumField0.1.0 : 22.10.2019Feature: First version ofFormclassFeature:CharFieldFeature:IntegerFieldFeature:FloatFieldFeature:DecimalFieldFeature:DateFieldFeature:TimeFieldFeature:DateTimeFieldFeature:DurationFieldFeature:RegexFieldFeature:EmailFieldFeature:BooleanFieldFeature:RegexFieldFeature:FieldListFeature:FormFieldFeature:FormFieldList