Extensions for the DRF
Project description
DRF Ext (Django REST Framework Extensions)
Extensions for the DRF
Installation:
pip install drf-ext
Features/Extensions:
- Nested model serializer saving (
create/update) - Declaration of non-required fields
- Add multiple common parameters to a set of fields
- Check fields' existence on de-serialization (
create/update) - Check any field's existence among a set of fields on de-serialization (
create/update) - Several frequently used utilities
Available objects:
Metaclasses:
NestedCreateUpdateMetaclass: provides nested serializer writes oncreateandupdate.FieldOptionsMetaclass: provides various field declaration options for different scenarios.ExtendedSerializerMetaclass: contains bothNestedCreateUpdateMetaclassandFieldOptionsMetaclass.InheritableExtendedSerializerMetaclass:ExtendedSerializerMetaclasswith inheritance support.
Mixins:
NestedCreateUpdateMixin: provides nested serializer writes oncreateandupdate(used byNestedCreateUpdateMetaclass), see example below.
Utilities:
update_error_dict: allows updating aValidationErrorerror dict with provided key/value.exc_dict_has_keys: tests whether given key(s) are in the exception error dict (e.g.ValidationError).get_request_user_on_serializer: gets the current user object from inside the serializer.
NOTE: All of the above are import-able from drf_ext e.g.:
from drf_ext import NestedCreateUpdateMetaclass, update_error_dict
Examples:
Assuming the following models.py:
from django.db import models
from django.contrib.auth.models import User
class Tag(models.Model):
name = models.CharField(max_length=12)
class Address(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
related_name="address",
null=True,
blank=True,
)
tags = models.ManyToManyField(Tag, related_name="addresses", blank=True)
state = models.CharField(max_length=2)
zip_code = models.CharField(max_length=12)
class Client(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
related_name="client",
null=False,
blank=False,
)
Metaclasses/mixins:
NestedCreateUpdateMetaclass/NestedCreateUpdateMixin:
serializers.py:
from rest_framework import serializers
from drf_ext import NestedCreateUpdateMetaclass
class AddressSerializer(serializers.ModelSerializer):
class Meta:
model = Address
fields = "__all__"
class UserSerializer(
serializers.ModelSerializer, metaclass=NestedCreateUpdateMetaclass
):
address = AddressSerializer()
class Meta:
model = User
fields = "__all__"
Sample POST request:
data = {
"username": "my_username",
"password": "my_password",
"address": {
"state": "CA",
"zip_code": "12345",
"tags": [1, 3, 7] # `pk` of `Tag` objects
}
}
client.post("/users/", data=data)
Sample PUT/PATCH request:
data = {
"address": {
"_pk": 2, # `pk` of the related `address`
"state": "CA",
"zip_code": "12345",
"tags": [9, 24, 56]
}
}
client.patch("/users/1/", data=data)
NOTE: drf_ext uses the existence of _pk field to track
whether it's a new nested object creation or an update. So if
_pk is omitted it would taken as new nested object creation
request.
This _pk write-only field is automatically
injected to all nested serializers by the metaclass. But if
one is using the NestedCreateUpdateMixin, they need to
explicitly define the field on the nested serializer e.g.:
class AddressSerializer(serializers.ModelSerializer):
_pk = serializers.IntegerField(write_only=True, required=False) # here
class Meta:
model = Address
fields = ("pk", "_pk", "state", "zip_code")
read_only_fields = ("pk",)
class UserSerializer(NestedCreateUpdateMixin, serializers.ModelSerializer):
address = AddressSerializer()
class Meta:
model = User
fields = "__all__"
Everything else remains the same as NestedCreateUpdateMetaclass.
FieldOptionsMetaclass:
required_fields_on_create, required_fields_on_update, required_fields_on_create_any, required_fields_on_update_any:
class AddressSerializer(serializers.ModelSerializer, metaclass=FieldOptionsMetaclass):
class Meta:
model = Address
fields = ("pk", "state", "zip_code")
read_only_fields = ("pk",)
# These fields are required on POST request i.e. on creation of object
required_fields_on_create = ("state", "zip_code")
# These fields are required on PUT/PATCH request i.e. on update of object
required_fields_on_update = ("zip_code",)
class UserSerializer(
serializers.ModelSerializer, metaclass=FieldOptionsMetaclass
):
address = AddressSerializer()
class Meta:
model = User
fields = (
"pk", "address", "username", "email",
"password", "first_name", "last_name",
)
read_only_fields = ("pk",)
# At least one of these fields are required on POST request
required_fields_on_create_any = ("first_name", "last_name")
# At least one of these fields are required on PUT/PATCH request
required_fields_on_update_any = ("address", "username", "email")
non_required_fields:
class AddressSerializer(serializers.ModelSerializer, metaclass=FieldOptionsMetaclass):
class Meta:
model = Address
fields = ("pk", "state", "zip_code")
read_only_fields = ("pk",)
# The mentioned fields are made *non-required*, like providing
# the `required=False` parameter on them. All fields are taken
# as required, unless model has `blank=True` on the field, or
# explicitly mentioned with `required=False`. This will allow
# to control that from a single place. Also, this would make
# working with `required_fields_on_create` and related options
# (see above) easier to follow as users can decide to make a
# field mandatory in POST but not in PUT/PATCH and vice versa,
# which allows for a finer control over fields.
non_required_fields = ("state", "zip_code")
NOTE: If non_required_fields is not provided, all fields mentioned
in fields (without exclude-ed ones) are made non-required. To use
the default option of DRF, one can set non_required_fields to an empty
iterable e.g.:
class AddressSerializer(serializers.ModelSerializer, metaclass=FieldOptionsMetaclass):
class Meta:
model = Address
fields = ("pk", "state", "zip_code")
non_required_fields = ()
common_field_params:
class AddressSerializer(serializers.ModelSerializer, metaclass=FieldOptionsMetaclass):
class Meta:
model = Address
fields = ("pk", "state", "zip_code")
read_only_fields = ("pk",)
# `common_field_params` allows to add some common parameters
# to a set of fields. This must be a `dict` with the keys
# being an (hashable) iterable e.g. `tuple` and values being
# a `dict` of parameter-values.
common_field_params = {
("state", zip_code"): {
"allow_blank": False,
"trim_whitespace": True,
},
# Using a single field is also fine (this works similar
# to the default `extra_kwargs` in that case).
("state",): {
"max_length": 2,
},
}
ExtendedSerializerMetaclass:
If you want to use all features from NestedCreateUpdateMetaclass and
FieldOptionsMetaclass mentioned above, use this metaclass:
class UserSerializer(serializers.ModelSerializer, metaclass=ExtendedSerializerMetaclass):
address = AddressSerializer()
class Meta:
model = Address
fields = "__all__"
required_fields_on_create = ("username, "password",)
required_fields_on_update_any = ("first_name", "last_name", "email")
Sample POST request:
data = {
"username": "my_username",
"password": "my_password",
"address": {
"state": "CA",
"zip_code": "12345"
}
}
client.post("/users/", data=data)
InheritableExtendedSerializerMetaclass:
Works exactly like ExtendedSerializerMetaclass. This one should be
used to include all the attributes defined in superclasses (ignoring
the dunder and Meta attributes, and callables).
This is designed to be used instead of ExtendedSerializerMetaclass
when e.g. a (common) base class contains field definitions that are
to be inherited by all child classes. For example:
class Common:
field = serializers.IntegerField()
class Serializer(
serializers.ModelSerializer,
metaclass=InheritableExtendedSerializerMetaclass
):
# `field` will be injected here like it were defined
# on this class body.
...
Utilities:
update_error_dict:
errors = {}
if ...:
# Following will result in `errors` being:
# `{"field": ["Error message"]}`
update_error_dict(errors, "field", "Error message")
if ...:
# Following will result in `errors` being:
# `{"field": ["Error message", "New error message"]}`
update_error_dict(errors, "field", "New error message")
if errors:
raise ValidationError(errors)
exc_dict_has_keys:
exc = ValidationError({
"field_1": ["msg", "new msg"],
"field_2": ["msg"],
})
exc_dict_has_keys(exc, ("field_1", "field_2")) # returns `True`
exc_dict_has_keys(exc, "field_1") # returns `True`
exc_dict_has_keys(exc, ("field_1", "field_2", "field_3")) # returns `False`
exc_dict_has_keys(exc, "field_3") # returns `False`
get_request_user_on_serializer:
class MySerializer(serializers.Serializer):
...
...
def create(self, validated_data):
# Get the user sending this request
user = get_request_user_on_serializer(self)
Development:
-
Install
devdependencies:pip install drf-ext[dev] -
Run tests:
drf_ext/tests$ PYTHONPATH=.. pytest
License:
MIT
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file drf-ext-0.1.1.tar.gz.
File metadata
- Download URL: drf-ext-0.1.1.tar.gz
- Upload date:
- Size: 3.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/46.0.0 requests-toolbelt/0.9.1 tqdm/4.40.2 CPython/3.8.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d71fad8810c88171e2d94a4aec8ef672277f96f85d3d31498e5b1d4b95017ea9
|
|
| MD5 |
0e0b0468bfc09731683df3918e2ccde0
|
|
| BLAKE2b-256 |
111bef4d4dab8aea13d39eef3a3fc6e3fd1dc3cbd170b549171e500f08475d48
|
File details
Details for the file drf_ext-0.1.1-py3-none-any.whl.
File metadata
- Download URL: drf_ext-0.1.1-py3-none-any.whl
- Upload date:
- Size: 18.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/46.0.0 requests-toolbelt/0.9.1 tqdm/4.40.2 CPython/3.8.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1190b8dbeb5efeffe046201ce5f65f855dfad54cab01acb56d1e6d8d870f8f8f
|
|
| MD5 |
6984bf08b1ef9df595571ae8db0a539f
|
|
| BLAKE2b-256 |
21f7230200af1bd7e3cc1d36e39f02d40631082522dd1b34a15e1e7144a3be84
|