Worf (Wade's Own Rest Framework): A more djangonic approach
Project description
Worf
A more Djangonic approach to Django REST APIs.
Full documentation for the project is available at https://memory-alpha.fandom.com/wiki/Worf.
Overview
Worf is a small Django API framework that lets you quickly build out an api using simple model methods. Contributions welcome.
This project is stable but will change drastically.
Roadmap
- Abstracting serializers away from model methods
- More support for different HTTP methods
- Support for user-generated validators
- Better file upload support
- Better test coverage
- Browsable API docs
API Method
Views execute the api_method
in order to return data.
Example API Method:
def api(self):
return [
'camelCaseField': self.camel_case_field,
...
]
Serializers
from worf.serializers import Serializer
Legacy Serializers
Views append _update_fields
to the api_method
property. This model method will
be executed on PATCH
requests. PATCH
requests are supported in DetailUpdateAPI
.
If you do not want to support PATCH
on a particular endpoint, use DetailAPI
.
Only fields included in the serializer method will be used to PATCH
models.
If a PATCH
request is made with fields that are not in the serializer, the
response will be HTTP 400
.
Example Serializer:
def api_update_fields(self):
return [
'field_for_update_1',
'field_for_update_2'
...
]
Example
The following example provides you with an api that does the following:
- Only allows authenticated users to access the endpoints
- Provides a list of books, with
POST
support to create a new book - Provides an endpoint for each book's detail endpoint, with
PATCH
support
A more complete example will demonstrate the additional built-in capabilities, including search, pagination, ordering, and the other things Worf can do.
Model Example
class Book(models.Model):
title = models.CharField(max_length=128)
author_name = models.CharField(max_length=128)
published_at = models.DateField()
# Legacy serializer style - don't use this
def api_update_fields(self):
return [
"title",
"author_name",
]
def api(self):
return dict(
title=self.title,
authorName=self.author_name,
published_at=self.published_at,
)
View Book Example
from worf.views import ListAPI, DetailPatchAPI
class BookList(ListCreateAPI):
permissions = [Authenticated]
model = Book
serializer = BookSerializer # Omit to use legacy serializer
class BookDetail(DetailPatchAPI):
permissions = [Authenticated]
model = Book
serializer = BookSerializer # Omit to use legacy serializer
URLs
...
path("api/v1/", include([
path("books/", BookList.as_view()),
path("books/<int:id>/", BookDetail.as_view()),
])
),
...
View Reference
worf.permissions
Permissions functions. These functions extend the API View, so they require
self
to be defined as a parameter. This is done in order to allow access to
self.request
during permission testing.
If permissions should be granted, functions should return int(200)
.
If permissions fail, they should return an HTTPExcption
worf.validators
Provides ValidationMixin
which AbstractBaseAPI
inherits from. Will perform
some coercion on self.bundle
, potentially resulting in a different bundle than
what was originally passed to the view.
worf.views
AbstractBaseAPI
Provides the basic functionality of both BaseList and BaseDetail APIs. It is not recommended to use this abstract view directly.
Class Attributes
name | required | type | description |
---|---|---|---|
bundle_name |
no | str |
Default: None If set, the returned data will use this name. I.e., {bundle_name : return_data} |
model |
yes | class |
An uninstantiated django.db.models.model class. |
permissions |
yes | list of permissions classes |
Will return appropriate HTTP status code based on the definition of the permission class. |
HTTP Methods
- GET is always supported.
ListAPI
name | required | type | description |
---|---|---|---|
api_method |
no | str |
Default: api . Must refer to a model method. This method is used to return data for GET requests. |
payload_key |
no | str |
Default: model._meta.verbose_name_plural . Use in order to rename the key for the results array |
filters |
no | dict |
Default: {} . Pass key/value pairs that you wish to further filter the queryset beyond the lookup_url_kwarg |
lookup_field |
no | str |
Use these two settings in tandem in order to filter get_queryset based on a URL field. lookup_url_kwarg is required if this is set. |
lookup_url_kwarg |
no | str |
Use these two settings in tandem in order to filter get_queryset based on a URL field. lookup_field is required if this is set. |
ordering |
no | list |
Default: [] . Pass a list of fields to default the queryset order by. |
filter_fields |
no | list |
Default: None . Pass a list of fields to support filtering via query params. |
search_fields |
no | dict or bool |
Default: None . Pass a dict with and /or fields to search via the q query param. |
sort_fields |
no | list |
Default: None . Pass a list of fields to support sorting via the sort query param. |
results_per_page |
no | int |
Default: 25. Sets the number of results returned for each page. |
Search
Setting search_fields
to True
will enable search based on url parameters.
Parameters in the URL must be camelCase and exactly match the snake_case model
field.
To allow full text search, set to a dictionary of two lists: 'or' & 'and'. Each
list will be assembled using either Q.OR
or Q.AND
. Fields in each list
must be composed of django filter lookup argument names.
Pagination
All ListAPI views are paginated and include a pagination
json object.
Use results_per_page
to set custom limit for pagination. Default 25.
get_queryset()
method
This method will use lookup_url_kwarg
and lookup_field
to filter results.
You should not need to override get_queryset. Instead, set the optional variables
listed above to configure the queryset.
Return
- A list of the
ListAPI.{model}.{instance}.api()
method by default. - If
lookup_field
andlookup_url_kwarg
fields are set, it will return a filtered list.
{
model_verbose_name: [
instance1.api(),
...
]
}
ListCreateAPI
Adds post
method to handle creation. Otherwise identical to ListAPI
. In most
cases, you will need to override the post method in order to create objects
properly. Do this by performing whatever logic you need to do, then update the
self.bundle
with whatever properties you need to set on the object. Then call
return super().post(request, *args, **kwargs)
.
DetailAPI
name | default | type | description |
---|---|---|---|
api_method |
api |
str |
Must refer to a model method. This method is used to return data for GET requests. Additionally, {api_method}_update_fields will be called to execute PATCH requests. |
lookup_field |
id |
str |
Override with the lookup field used to filter the model. Defaults to id |
lookup_url_kwarg |
id |
str |
Override with the name of the parameter passed to the view by the URL route. Defaults to id |
get_instance()
method
This method uses lookup_field
and lookup_url_kwargs
to return a model instance.
You may prefer to override this method, for example in a case when you are using request.user to return an instance.
DetailUpdateAPI
Adds patch
method to handle updates. Otherwise identical to DetailAPI
. Runs
validators automatically. You should not need to override update. If the
api_update_fields
model method returns an empty list, HTTP 422 will be raised.
Additional Information
Converting 🐍 & 🐪
Worf expects all your model fields to be defined in snake case, and your api's json objects to be camel case. This conversion is handled in worf.casing
.
Limitations With 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:
# profiles.models.freelancers.Freelancer
freelance_fulltime = models.CharField(...)
freelancer_id = UUIDField(...)
API_strict = ...
This will be strictly translated by the API. Acronyms are not considered:
freelance_fulltime == freelaneFulltime
freelancer_id == freelancerId
API_strict == apiStrict
At this time, we still manually define the camelCase
names in the model api()
method. This will eventually be removed in favor of automated case conversion,
but it has and will continue to present an opportunity for error, for now.
self.bundle
is loaded from json
The dispatch
method is run by Django when the view is called. In our version
of dispatch, we interpret any request.body
as 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.bundle
is set on a class level, it is available to all methods inside
the view. We perform type coercion during validation, so self.bundle
will
be changed during processing. You may also append or remove attributes to the
bundle before saving the object via post
, patch
, or other methods.
Naming Things
We refer to the json object that is sent and received by the api differently in this codebase for clarity:
bundle
is what we send to the backend.payload
is what the backend returns.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file worf-0.3.7.tar.gz
.
File metadata
- Download URL: worf-0.3.7.tar.gz
- Upload date:
- Size: 25.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.2 importlib_metadata/4.6.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.61.2 CPython/3.8.9
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | ebec51ef8be8545aee740855a245704aab2cd2bc08c3b574cc05be7848de023f |
|
MD5 | 024e21910413f1a498ea87e1cbfda709 |
|
BLAKE2b-256 | 008a0612d428bde0115c5bcb574ca13b7162d0d230697860e0af3eced52256a6 |
File details
Details for the file worf-0.3.7-py3-none-any.whl
.
File metadata
- Download URL: worf-0.3.7-py3-none-any.whl
- Upload date:
- Size: 27.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.2 importlib_metadata/4.6.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.61.2 CPython/3.8.9
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 150a1ac0b479a3c7cdedba03dcf44a41160601ee781365a3ec4a1f4064025c5c |
|
MD5 | b59a5057bf38abd704776f597a33ddf2 |
|
BLAKE2b-256 | de91c35a5519c374d098f26c3d20553924296f64198b0f2d2348ef2955f44f06 |