Add LazyLoadModelSerializer

This commit is contained in:
Łukasz Wieczorek 2022-07-29 13:07:13 +02:00
parent a1b35bb44b
commit 56985d3e1f
2 changed files with 85 additions and 14 deletions

View File

@ -16,6 +16,7 @@ import traceback
from collections import OrderedDict, defaultdict from collections import OrderedDict, defaultdict
from collections.abc import Mapping from collections.abc import Mapping
from django.apps import apps
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import models from django.db import models
@ -1014,6 +1015,27 @@ class ModelSerializer(Serializer):
return instance return instance
@classmethod
def check_meta(cls):
"""
Check presence of Meta and Meta.model and test whether Meta.model is
not abstract.
"""
assert hasattr(cls, 'Meta'), (
'Class {serializer_class} missing "Meta" attribute'.format(
serializer_class=cls.__class__.__name__
)
)
assert hasattr(cls.Meta, 'model'), (
'Class {serializer_class} missing "Meta.model" attribute'.format(
serializer_class=cls.__class__.__name__
)
)
if model_meta.is_abstract_model(cls.Meta.model):
raise ValueError(
'Cannot use ModelSerializer with Abstract Models.'
)
# Determine the fields to apply... # Determine the fields to apply...
def get_fields(self): def get_fields(self):
@ -1024,20 +1046,7 @@ class ModelSerializer(Serializer):
if self.url_field_name is None: if self.url_field_name is None:
self.url_field_name = api_settings.URL_FIELD_NAME self.url_field_name = api_settings.URL_FIELD_NAME
assert hasattr(self, 'Meta'), ( self.check_meta()
'Class {serializer_class} missing "Meta" attribute'.format(
serializer_class=self.__class__.__name__
)
)
assert hasattr(self.Meta, 'model'), (
'Class {serializer_class} missing "Meta.model" attribute'.format(
serializer_class=self.__class__.__name__
)
)
if model_meta.is_abstract_model(self.Meta.model):
raise ValueError(
'Cannot use ModelSerializer with Abstract Models.'
)
declared_fields = copy.deepcopy(self._declared_fields) declared_fields = copy.deepcopy(self._declared_fields)
model = getattr(self.Meta, 'model') model = getattr(self.Meta, 'model')
@ -1664,3 +1673,41 @@ class HyperlinkedModelSerializer(ModelSerializer):
field_kwargs = get_nested_relation_kwargs(relation_info) field_kwargs = get_nested_relation_kwargs(relation_info)
return field_class, field_kwargs return field_class, field_kwargs
class LazyLoadModelSerializer(ModelSerializer):
"""
Lazy loads the model from django.apps.apps during instance creation.
"""
@classmethod
def get_model(cls):
"""
Splits the app_label and model_name from Meta.model and fetches model
from model registry.
"""
try:
app_label, model_name = cls.Meta.model.split(".")
except ValueError:
raise ValueError(
"Please provide the app_label and model_name in dot notation."
)
return apps.get_model(app_label, model_name=model_name)
@classmethod
def load_model(cls):
"""
Loads the model and sets it.
"""
cls.check_meta()
if not isinstance(cls.Meta.model, str):
return
cls.Meta.model = cls.get_model()
def __new__(cls, *args, **kwargs):
"""
Overridden to load the model.
"""
cls.load_model()
return super().__new__(cls, *args, **kwargs)

View File

@ -6,6 +6,7 @@ from collections import ChainMap
from collections.abc import Mapping from collections.abc import Mapping
import pytest import pytest
from django.apps import apps
from django.db import models from django.db import models
from rest_framework import exceptions, fields, relations, serializers from rest_framework import exceptions, fields, relations, serializers
@ -669,6 +670,11 @@ class TestDeclaredFieldInheritance:
assert len(Child._declared_fields) == 1 assert len(Child._declared_fields) == 1
assert len(Grandchild._declared_fields) == 1 assert len(Grandchild._declared_fields) == 1
@staticmethod
def deregister_model(model_class):
app_models = apps.all_models[model_class._meta.app_label]
del app_models[model_class._meta.model_name]
def test_meta_field_disabling(self): def test_meta_field_disabling(self):
# Declaratively setting a field on a child class will *not* prevent # Declaratively setting a field on a child class will *not* prevent
# the ModelSerializer from generating a default field. # the ModelSerializer from generating a default field.
@ -691,6 +697,24 @@ class TestDeclaredFieldInheritance:
assert len(Child().get_fields()) == 2 assert len(Child().get_fields()) == 2
assert len(Grandchild().get_fields()) == 2 assert len(Grandchild().get_fields()) == 2
self.deregister_model(MyModel)
def test_lazy_load_model_serializer(self):
class MyModel(models.Model):
f1 = models.CharField(max_length=10)
f2 = models.CharField(max_length=10)
model_string = f"{MyModel._meta.app_label}.{MyModel._meta.model_name}"
class MyModelSerializer(serializers.LazyLoadModelSerializer):
class Meta:
model = model_string
fields = '__all__'
assert len(MyModelSerializer().get_fields()) == 3
self.deregister_model(MyModel)
def test_multiple_inheritance(self): def test_multiple_inheritance(self):
class A(serializers.Serializer): class A(serializers.Serializer):
field = serializers.CharField() field = serializers.CharField()