mirror of
https://github.com/Hopiu/django.git
synced 2026-05-24 06:43:52 +00:00
Since the original ones in django.db.models.loading were kept only for backwards compatibility, there's no need to recreate them. However, many internals of Django still relied on them. They were also imported in django.db.models. They never appear in the documentation, except a quick mention of get_models and get_app in the 1.2 release notes to document an edge case in GIS. I don't think that makes them a public API. This commit doesn't change the overall amount of global state but clarifies that it's tied to the app_cache object instead of hiding it behind half a dozen functions.
202 lines
6.8 KiB
Python
202 lines
6.8 KiB
Python
"""
|
|
Module for abstract serializer/unserializer base classes.
|
|
"""
|
|
import warnings
|
|
|
|
from django.apps import app_cache
|
|
from django.db import models
|
|
from django.utils import six
|
|
|
|
|
|
class SerializerDoesNotExist(KeyError):
|
|
"""The requested serializer was not found."""
|
|
pass
|
|
|
|
|
|
class SerializationError(Exception):
|
|
"""Something bad happened during serialization."""
|
|
pass
|
|
|
|
|
|
class DeserializationError(Exception):
|
|
"""Something bad happened during deserialization."""
|
|
pass
|
|
|
|
|
|
class Serializer(object):
|
|
"""
|
|
Abstract serializer base class.
|
|
"""
|
|
|
|
# Indicates if the implemented serializer is only available for
|
|
# internal Django use.
|
|
internal_use_only = False
|
|
|
|
def serialize(self, queryset, **options):
|
|
"""
|
|
Serialize a queryset.
|
|
"""
|
|
self.options = options
|
|
|
|
self.stream = options.pop("stream", six.StringIO())
|
|
self.selected_fields = options.pop("fields", None)
|
|
self.use_natural_keys = options.pop("use_natural_keys", False)
|
|
if self.use_natural_keys:
|
|
warnings.warn("``use_natural_keys`` is deprecated; use ``use_natural_foreign_keys`` instead.",
|
|
PendingDeprecationWarning)
|
|
self.use_natural_foreign_keys = options.pop('use_natural_foreign_keys', False) or self.use_natural_keys
|
|
self.use_natural_primary_keys = options.pop('use_natural_primary_keys', False)
|
|
|
|
self.start_serialization()
|
|
self.first = True
|
|
for obj in queryset:
|
|
self.start_object(obj)
|
|
# Use the concrete parent class' _meta instead of the object's _meta
|
|
# This is to avoid local_fields problems for proxy models. Refs #17717.
|
|
concrete_model = obj._meta.concrete_model
|
|
for field in concrete_model._meta.local_fields:
|
|
if field.serialize:
|
|
if field.rel is None:
|
|
if self.selected_fields is None or field.attname in self.selected_fields:
|
|
self.handle_field(obj, field)
|
|
else:
|
|
if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
|
|
self.handle_fk_field(obj, field)
|
|
for field in concrete_model._meta.many_to_many:
|
|
if field.serialize:
|
|
if self.selected_fields is None or field.attname in self.selected_fields:
|
|
self.handle_m2m_field(obj, field)
|
|
self.end_object(obj)
|
|
if self.first:
|
|
self.first = False
|
|
self.end_serialization()
|
|
return self.getvalue()
|
|
|
|
def start_serialization(self):
|
|
"""
|
|
Called when serializing of the queryset starts.
|
|
"""
|
|
raise NotImplementedError('subclasses of Serializer must provide a start_serialization() method')
|
|
|
|
def end_serialization(self):
|
|
"""
|
|
Called when serializing of the queryset ends.
|
|
"""
|
|
pass
|
|
|
|
def start_object(self, obj):
|
|
"""
|
|
Called when serializing of an object starts.
|
|
"""
|
|
raise NotImplementedError('subclasses of Serializer must provide a start_object() method')
|
|
|
|
def end_object(self, obj):
|
|
"""
|
|
Called when serializing of an object ends.
|
|
"""
|
|
pass
|
|
|
|
def handle_field(self, obj, field):
|
|
"""
|
|
Called to handle each individual (non-relational) field on an object.
|
|
"""
|
|
raise NotImplementedError('subclasses of Serializer must provide an handle_field() method')
|
|
|
|
def handle_fk_field(self, obj, field):
|
|
"""
|
|
Called to handle a ForeignKey field.
|
|
"""
|
|
raise NotImplementedError('subclasses of Serializer must provide an handle_fk_field() method')
|
|
|
|
def handle_m2m_field(self, obj, field):
|
|
"""
|
|
Called to handle a ManyToManyField.
|
|
"""
|
|
raise NotImplementedError('subclasses of Serializer must provide an handle_m2m_field() method')
|
|
|
|
def getvalue(self):
|
|
"""
|
|
Return the fully serialized queryset (or None if the output stream is
|
|
not seekable).
|
|
"""
|
|
if callable(getattr(self.stream, 'getvalue', None)):
|
|
return self.stream.getvalue()
|
|
|
|
|
|
class Deserializer(six.Iterator):
|
|
"""
|
|
Abstract base deserializer class.
|
|
"""
|
|
|
|
def __init__(self, stream_or_string, **options):
|
|
"""
|
|
Init this serializer given a stream or a string
|
|
"""
|
|
self.options = options
|
|
if isinstance(stream_or_string, six.string_types):
|
|
self.stream = six.StringIO(stream_or_string)
|
|
else:
|
|
self.stream = stream_or_string
|
|
# hack to make sure that the models have all been loaded before
|
|
# deserialization starts (otherwise subclass calls to get_model()
|
|
# and friends might fail...)
|
|
app_cache.get_apps()
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
"""Iteration iterface -- return the next item in the stream"""
|
|
raise NotImplementedError('subclasses of Deserializer must provide a __next__() method')
|
|
|
|
|
|
class DeserializedObject(object):
|
|
"""
|
|
A deserialized model.
|
|
|
|
Basically a container for holding the pre-saved deserialized data along
|
|
with the many-to-many data saved with the object.
|
|
|
|
Call ``save()`` to save the object (with the many-to-many data) to the
|
|
database; call ``save(save_m2m=False)`` to save just the object fields
|
|
(and not touch the many-to-many stuff.)
|
|
"""
|
|
|
|
def __init__(self, obj, m2m_data=None):
|
|
self.object = obj
|
|
self.m2m_data = m2m_data
|
|
|
|
def __repr__(self):
|
|
return "<DeserializedObject: %s.%s(pk=%s)>" % (
|
|
self.object._meta.app_label, self.object._meta.object_name, self.object.pk)
|
|
|
|
def save(self, save_m2m=True, using=None):
|
|
# Call save on the Model baseclass directly. This bypasses any
|
|
# model-defined save. The save is also forced to be raw.
|
|
# raw=True is passed to any pre/post_save signals.
|
|
models.Model.save_base(self.object, using=using, raw=True)
|
|
if self.m2m_data and save_m2m:
|
|
for accessor_name, object_list in self.m2m_data.items():
|
|
setattr(self.object, accessor_name, object_list)
|
|
|
|
# prevent a second (possibly accidental) call to save() from saving
|
|
# the m2m data twice.
|
|
self.m2m_data = None
|
|
|
|
|
|
def build_instance(Model, data, db):
|
|
"""
|
|
Build a model instance.
|
|
|
|
If the model instance doesn't have a primary key and the model supports
|
|
natural keys, try to retrieve it from the database.
|
|
"""
|
|
obj = Model(**data)
|
|
if (obj.pk is None and hasattr(Model, 'natural_key') and
|
|
hasattr(Model._default_manager, 'get_by_natural_key')):
|
|
natural_key = obj.natural_key()
|
|
try:
|
|
obj.pk = Model._default_manager.db_manager(db).get_by_natural_key(*natural_key).pk
|
|
except Model.DoesNotExist:
|
|
pass
|
|
return obj
|