119 lines
4.2 KiB
Python
119 lines
4.2 KiB
Python
"""Commonly used API serializers."""
|
|
from django.conf import settings
|
|
from django.urls.base import reverse
|
|
from rest_framework import relations
|
|
from rest_framework import serializers
|
|
|
|
|
|
def make_absolute_url(url: str) -> str:
|
|
"""Turn a host-absolute URL into a fully absolute URL.
|
|
|
|
For simplicity, assumes we're using HTTPS, unless in DEBUG mode,
|
|
and not running on some URL prefix.
|
|
"""
|
|
from django.contrib.sites.models import Site
|
|
from urllib.parse import urljoin
|
|
|
|
site: Site = Site.objects.get_current()
|
|
return urljoin(f'http{not settings.DEBUG and "s" or ""}://{site.domain}/', url)
|
|
|
|
|
|
class IdManyRelatedField(serializers.ManyRelatedField):
|
|
"""Append an "_ids" suffix to names of m2m related fields."""
|
|
|
|
field_name_suffix = '_ids'
|
|
|
|
def bind(self, field_name, parent):
|
|
"""Bind the field is bound to the serializer.
|
|
|
|
Changes the source so that the original field name is used
|
|
(removes the "_ids" suffix).
|
|
"""
|
|
self.source = field_name[: -len(self.field_name_suffix)]
|
|
super().bind(field_name, parent)
|
|
|
|
|
|
class IdPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
|
|
"""Append an "_id" suffix to names of related fields.
|
|
|
|
Only works together the IdModelSerializer.
|
|
"""
|
|
|
|
many_related_field_class = IdManyRelatedField
|
|
field_name_suffix = '_id'
|
|
|
|
@classmethod
|
|
def many_init(cls, *args, **kwargs): # noqa: D102
|
|
list_kwargs = {'child_relation': cls(*args, **kwargs)}
|
|
for key in kwargs:
|
|
if key in relations.MANY_RELATION_KWARGS:
|
|
list_kwargs[key] = kwargs[key]
|
|
return cls.many_related_field_class(**list_kwargs)
|
|
|
|
def bind(self, field_name, parent):
|
|
"""Bind the field is bound to the serializer.
|
|
|
|
Changes the source so that the original field name is used
|
|
(removes the "_id" suffix).
|
|
"""
|
|
if field_name:
|
|
self.source = field_name[: -len(self.field_name_suffix)]
|
|
super().bind(field_name, parent)
|
|
|
|
|
|
class IdModelSerializer(serializers.ModelSerializer):
|
|
"""Change field names of related fields to FIELD_NAME_id/_ids."""
|
|
|
|
serializer_related_field = IdPrimaryKeyRelatedField
|
|
|
|
admin_url = serializers.SerializerMethodField()
|
|
view_on_site_url = serializers.SerializerMethodField()
|
|
|
|
def get_admin_url(self, obj):
|
|
"""Return an absolute admin URL of the object."""
|
|
url_name = f'admin:{obj._meta.app_label}_{obj.__class__.__name__.lower()}_change'
|
|
return make_absolute_url(reverse(url_name, args=[obj.pk]))
|
|
|
|
def get_view_on_site_url(self, obj):
|
|
"""Return an absolute URL to the objects on the Studio website."""
|
|
obj_url = None
|
|
if hasattr(obj, 'get_absolute_url'):
|
|
obj_url = obj.get_absolute_url()
|
|
elif hasattr(obj, 'url'):
|
|
obj_url = obj.url
|
|
if not obj_url:
|
|
return None
|
|
return make_absolute_url(obj_url)
|
|
|
|
def get_fields(self): # noqa: D102
|
|
fields = super().get_fields()
|
|
new_fields = type(fields)()
|
|
|
|
for field_name, field in fields.items():
|
|
try:
|
|
field_name += field.field_name_suffix
|
|
except AttributeError:
|
|
pass
|
|
new_fields[field_name] = field
|
|
return new_fields
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Set some field flags in bulk, override some widgets in API docs.
|
|
|
|
Handles the following new Meta attributes:
|
|
|
|
writable_fields: list of writable field names
|
|
raw_id_fields: list, overrides heavy selects with simple inputs in API docs
|
|
labels: dict, overrides field labels in API docs
|
|
"""
|
|
super().__init__(*args, **kwargs)
|
|
meta = self.__class__.Meta
|
|
for field in self.fields:
|
|
if hasattr(meta, 'writable_fields') and field not in meta.writable_fields:
|
|
self.fields[field].read_only = True
|
|
if hasattr(meta, 'labels') and field in meta.labels:
|
|
self.fields[field].label = meta.labels[field]
|
|
# Prevent DRF from attempting to render inputs from all of the related model choices
|
|
if hasattr(meta, 'raw_id_fields') and field in meta.raw_id_fields:
|
|
self.fields[field].style = {'base_template': 'input.html'}
|