Package: worf
WorfWorf is a small Django API framework for building out REST APIs simply using
class-based views and serializers.Full documentation for the project is available athttps://memory-alpha.fandom.com/wiki/Worf.Table of contentsInstallationRequirementsRoadmapUsageSerializersPermissionsValidatorsViewsListAPIDetailAPICreateAPIUpdateAPIActionAPIDeleteAPIBrowsable APIBundle loadingDebuggingField casingFile uploadsInternal namingSettingsCreditsInstallationInstall using pip:pipinstallworfAddworfto yourINSTALLED_APPSsetting:INSTALLED_APPS=[..."worf",]RequirementsPython (3.9, 3.10, 3.11)Django (3.2, 4.0, 4.1)RoadmapAbstracting serializers away from model methodsBrowsable APIDeclarative marshmallow-based serializationFile upload supportSupport for PATCH/PUT methodsBetter test coverageDocumentation generationSupport for user-generated validatorsUsageThe following examples provides you with an API that does the following:Only allows authenticated users to access the endpointsProvides a list of books, withPOSTsupport to create a new bookProvides an endpoint for each book's detail endpoint, withPATCHsupportA more complete example will demonstrate the additional built-in capabilities,
including search, pagination, ordering, and the other things Worf can do.# models.pyclassBook(models.Model):title=models.CharField(max_length=128)author_name=models.CharField(max_length=128)published_at=models.DateField()# serializers.pyfromworf.serializersimportSerializerclassBookSerializer(Serializer):classMeta:fields=["id","title","author_name","published_at",]# views.pyfromworf.permissionsimportAuthenticatedfromworf.viewsimportActionAPI,DeleteAPI,DetailAPI,ListAPI,UpdateAPIclassBookList(CreateAPI,ListAPI):model=Bookserializer=BookSerializer(only=["id","title"])permissions=[Authenticated]classBookDetail(ActionAPI,DeleteAPI,UpdateAPI,DetailAPI):model=Bookserializer=BookSerializerpermissions=[Authenticated]actions=["publish"]# urls.pypath("api/",include([path("books/",BookList.as_view()),path("books/<int:id>/",BookDetail.as_view()),path("books/<int:id>/<str:action>/",BookDetail.as_view()),])),SerializersWorf serializers are basicallymarshmallow schemaswith some tweaks to improve support for Django models, and supply extra defaults.fromworf.serializersimportfields,SerializerclassBookSerializer(Serializer):author=fields.Nested(AuthorSerializer)tags=fields.Nested(TagSerializer,many=True)classMeta:fields=["id","title","content","image","url","author","tags",]Worf serializers build on top of marshmallow to make them a little easier to use
in Django, primarily, we add support for using theNestedfield with related
managers, and setting default serializer options via settings:WORF_SERIALIZER_DEFAULT_OPTIONS={"dump_only":["id","created_at","deleted_at","updated_at",]}PermissionsPermissions are callable classes that can be found inworf.permissions, they're passed
therequestandkwargsfrom the view, and raise an exception if the check fails.ValidatorsValidation handling can be found inworf.validators.The basics come fromValidateFieldswhichAbstractBaseAPIinherits from, it
performs some coercion onself.bundle, potentially resulting in a different
bundle than what was originally passed to the view.ViewsAbstractBaseAPIProvides the basic functionality of API views.NameTypeDefaultDescriptionmodelclassNoneModel class.permissionslist[]List of permissions classes.serializerobjectNoneSerializer class or instance.Note:it is not recommended to use this abstract view directly.ListAPINameTypeDefaultDescriptionquerysetobjectmodel.objects.all()Queryset used to retrieve the results.lookup_fieldstrNoneFilterquerysetbased on a URL param,lookup_url_kwargis required if this is set.lookup_url_kwargstrNoneFilterquerysetbased on a URL param,lookup_fieldis required if this is set.payload_keystrverbose_name_pluralUse in order to rename the key for the results array.orderinglist[]Fields to default the queryset order by.filter_fieldslist[]Fields to support filtering via query params.include_fieldsdict/list[]Fields to support optionally including via theincludequery param.search_fieldslist[]Fields to full text search via theqquery param.sort_fieldslist[]Fields to support sorting via thesortquery param.per_pageint25Number of results returned for each page.max_per_pageintper_pageMax number of results to allow when passing theperPagequery param.Theget_querysetmethod will uselookup_url_kwargandlookup_fieldto filter results.
Youshouldnot need to overrideget_queryset. Instead, set the optional variables
listed above to configure the queryset.FilteringParameters in the URL must be camelCase and exactly match the snake_case model field.To allow full text search, set to a list of fields for django filter lookups.For a full list of supported lookups seehttps://django-url-filter.readthedocs.io.Include fieldsInclude fields is a list of fields to include when?include=skills,teamis passed.If passing a dict the values are passed through to eitherprefetch_relatedorselect_related.classProfileList(CreateAPI,ListAPI):model=Profileinclude_fields={"skills":Prefetch("skills"),"team":"team",}Search fieldsSearch fields is a list of fields that are used foricontainslookups via?q=.The?search=id,namequery param can be used to filtersearch_fields.PaginationAll ListAPI views are paginated and include apaginationjson object.Useper_pageto set custom limit for pagination. Default 25.DetailAPINameTypeDefaultDescriptionquerysetobjectmodel.objects.all()Queryset used to retrieve the results.lookup_fieldstridLookup field used to filter the model.lookup_url_kwargstridName of the parameter passed to the view by the URL route.Thisget_instance()method useslookup_fieldandlookup_url_kwargsto return a model instance.Youmayprefer to override this method, for example in a case when you are usingrequest.userto return an instance.CreateAPINameTypeDefaultDescriptioncreate_serializerobjectserializerSerializer class or instance.Adds apostmethod to handle creation, mix this into aListAPIview:classBookListAPI(CreateAPI,ListAPI):model=Bookserializer=BookSerializerValidation of creates is kind of sketchy right now, but the idea is that you'd
use the same serializer as you would for an update, unless you havecreate-onlyfields, in which case, you may want to create aBookCreateSerializer.UpdateAPINameTypeDefaultDescriptionquerysetobjectmodel.objects.all()Queryset used to retrieve the results.lookup_fieldstridLookup field used to filter the model.lookup_url_kwargstridName of the parameter passed to the view by the URL route.update_serializerobjectserializerSerializer class or instance.Addspatchandputmethods to handle updates, mix this into aDetailAPI.classBookDetailAPI(UpdateAPI,DetailAPI):model=Bookserializer=BookSerializerValidation of update fields is delegated to the serializer, any fields that are
writeable should be within thefieldsdefinition of the serializer, and not
marked asdump_only(read-only).ActionAPINameTypeDefaultDescriptionquerysetobjectmodel.objects.all()Queryset used to retrieve the results.lookup_fieldstridLookup field used to filter the model.lookup_url_kwargstridName of the parameter passed to the view by the URL route.actionslist[]List of action methods to support.Addsputendpoints keyed by a route param, mix this into aDetailAPIview:classBookDetailAPI(ActionAPI,DetailAPI):model=Bookserializer=BookSerializeractions=["publish"]Actions must exist as a method on either the model or the view, they are passed the
contents of the bundle as kwargs, and if the method accepts auserkwarg thenrequest.userwill be passed through too.DeleteAPINameTypeDefaultDescriptionquerysetobjectmodel.objects.all()Queryset used to retrieve the results.lookup_fieldstridLookup field used to filter the model.lookup_url_kwargstridName of the parameter passed to the view by the URL route.Adds adeletemethod to handle deletes, mix this into aDetailAPI.classBookDetailAPI(DeleteAPI,DetailAPI):model=BookDeletes return a 204 no content response, no serializer is required.Browsable APISimilar to other popular REST frameworks; Worf exposes a browsable API which adds
syntax highlighting, linkified URLs and supports Django Debug Toolbar.To override the default browser behaviour pass?format=json, ordisable the
feature entirely from settings.ThemeThe theme is built withTailwind, making it easy to customize the look-and-feel.For quick and easy branding, there are a couple ofsettings that tweak the navbar.To customize the markup create a template calledworf/api.htmlthat extends fromworf/base.html:# templates/worf/api.html{%extends"worf/base.html"%}{%blockbranding%}{{block.super}}<div>A warrior's drink!</div>{%endblock%}All of the blocks available in the base template can be used in yourapi.html.NameDescriptionbodyThe entire html<body>.brandingBranding section of the navbar.scriptJavaScript files for the page.styleCSS stylesheets for the page.titleTitle of the page.For more advanced customization you can choose not to haveapi.htmlextendbase.html.Bundle loadingThedispatchmethod is run by Django when the view is called. In our version
of dispatch, we interpret anyrequest.bodyas JSON, and convert all values
from camel to snake case at that time. You'll always be able to access bundle
attributes by their snake case variable name, and these attributes will exactly
match the model fields.self.bundleis set on a class level, it is available to all methods inside
the view. We perform type coercion during validation, soself.bundlewill
be changed during processing. You may also append or remove attributes to the
bundle before saving the object viapost,patch, or other methods.DebuggingWorf exposes the parsed bundle, lookup kwargs and skips some exception handlingwhen in debug mode.Field casingWorf expects all your model fields to be defined in snake case 🐍, and JSON
objects to be camel case 🐪 and that conversion is handled inworf.casing.We interpret camelCase,strictly, based on the database model. This means that
inappropriate naming of database fields will result in confusion.A quick example:freelance_fulltime=models.CharField(...)freelancer_id=models.UUIDField(...)API_strict=...This will be strictly translated by the API, and acronyms are not considered:freelance_fulltime == freelanceFulltimefreelancer_id == freelancerIdAPI_strict == apiStrictFile uploadsFile uploads are supported viamultipart/form-datarequests.Internal namingWe refer to the json object that is sent and received by the API differently in
this codebase for clarity:bundleis what we send to the backend.payloadis what the backend returns.SettingsNameDefaultDescriptionWORF_API_NAMEWorf APISeeBrowsable APIWORF_API_ROOT/api/SeeBrowsable APIWORF_BROWSABLE_APITrueSeeBrowsable APIWORF_DEBUGsettings.DEBUGSeeDebuggingCreditsWanted dead or aliveMade with 🥃 atGun.io