Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
444 lines (354 sloc) 16.3 KB
# All fields except for BlobField written by Jonas Haag <jonas@lophus.org>
from django.core.exceptions import ValidationError
from django.utils.importlib import import_module
from django.db import models
from django.db.models.fields.subclassing import Creator
from django.db.utils import IntegrityError
from django.db.models.fields.related import add_lazy_relation
__all__ = ('RawField', 'ListField', 'SetField', 'DictField',
'EmbeddedModelField', 'BlobField')
EMPTY_ITER = ()
class _FakeModel(object):
"""
An object of this class can pass itself off as a model instance
when used as an arguments to Field.pre_save method (item_fields
of iterable fields are not actually fields of any model).
"""
def __init__(self, field, value):
setattr(self, field.attname, value)
class RawField(models.Field):
"""
Generic field to store anything your database backend allows you
to. No validation or conversions are done for this field.
"""
def get_internal_type(self):
"""
Returns this field's kind. Nonrel fields are meant to extend
the set of standard fields, so fields subclassing them should
get the same internal type, rather than their own class name.
"""
return 'RawField'
class AbstractIterableField(models.Field):
"""
Abstract field for fields for storing iterable data type like
``list``, ``set`` and ``dict``.
You can pass an instance of a field as the first argument.
If you do, the iterable items will be piped through the passed
field's validation and conversion routines, converting the items
to the appropriate data type.
"""
def __init__(self, item_field=None, *args, **kwargs):
default = kwargs.get(
'default', None if kwargs.get('null') else EMPTY_ITER)
# Ensure a new object is created every time the default is
# accessed.
if default is not None and not callable(default):
kwargs['default'] = lambda: self._type(default)
super(AbstractIterableField, self).__init__(*args, **kwargs)
# Either use the provided item_field or a RawField.
if item_field is None:
item_field = RawField()
elif callable(item_field):
item_field = item_field()
self.item_field = item_field
# We'll be pretending that item_field is a field of a model
# with just one "value" field.
assert not hasattr(self.item_field, 'attname')
self.item_field.set_attributes_from_name('value')
def contribute_to_class(self, cls, name):
self.item_field.model = cls
self.item_field.name = name
super(AbstractIterableField, self).contribute_to_class(cls, name)
# If items' field uses SubfieldBase we also need to.
item_metaclass = getattr(self.item_field, '__metaclass__', None)
if item_metaclass and issubclass(item_metaclass, models.SubfieldBase):
setattr(cls, self.name, Creator(self))
if isinstance(self.item_field, models.ForeignKey) and isinstance(self.item_field.rel.to, basestring):
"""
If rel.to is a string because the actual class is not yet defined, look up the
actual class later. Refer to django.models.fields.related.RelatedField.contribute_to_class.
"""
def _resolve_lookup(_, resolved_model, __):
self.item_field.rel.to = resolved_model
self.item_field.do_related_class(self, cls)
add_lazy_relation(cls, self, self.item_field.rel.to, _resolve_lookup)
def _map(self, function, iterable, *args, **kwargs):
"""
Applies the function to items of the iterable and returns
an iterable of the proper type for the field.
Overriden by DictField to only apply the function to values.
"""
return self._type(function(element, *args, **kwargs)
for element in iterable)
def to_python(self, value):
"""
Passes value items through item_field's to_python.
"""
if value is None:
return None
return self._map(self.item_field.to_python, value)
def pre_save(self, model_instance, add):
"""
Gets our value from the model_instance and passes its items
through item_field's pre_save (using a fake model instance).
"""
value = getattr(model_instance, self.attname)
if value is None:
return None
return self._map(
lambda item: self.item_field.pre_save(
_FakeModel(self.item_field, item), add),
value)
def get_db_prep_save(self, value, connection):
"""
Applies get_db_prep_save of item_field on value items.
"""
if value is None:
return None
return self._map(self.item_field.get_db_prep_save, value,
connection=connection)
def get_db_prep_lookup(self, lookup_type, value, connection,
prepared=False):
"""
Passes the value through get_db_prep_lookup of item_field.
"""
# TODO/XXX: Remove as_lookup_value() once we have a cleaner
# solution for dot-notation queries.
# See: https://groups.google.com/group/django-non-relational/browse_thread/thread/6056f8384c9caf04/89eeb9fb22ad16f3).
if hasattr(value, 'as_lookup_value'):
value = value.as_lookup_value(self, lookup_type, connection)
return self.item_field.get_db_prep_lookup(
lookup_type, value, connection=connection, prepared=prepared)
def validate(self, values, model_instance):
try:
iter(values)
except TypeError:
raise ValidationError("Value of type %r is not iterable." %
type(values))
def formfield(self, **kwargs):
raise NotImplementedError("No form field implemented for %r." %
type(self))
class ListField(AbstractIterableField):
"""
Field representing a Python ``list``.
If the optional keyword argument `ordering` is given, it must be a
callable that is passed to :meth:`list.sort` as `key` argument. If
`ordering` is given, the items in the list will be sorted before
sending them to the database.
"""
_type = list
def __init__(self, *args, **kwargs):
self.ordering = kwargs.pop('ordering', None)
if self.ordering is not None and not callable(self.ordering):
raise TypeError("'ordering' has to be a callable or None, "
"not of type %r." % type(self.ordering))
super(ListField, self).__init__(*args, **kwargs)
def get_internal_type(self):
return 'ListField'
def pre_save(self, model_instance, add):
value = getattr(model_instance, self.attname)
if value is None:
return None
if value and self.ordering:
value.sort(key=self.ordering)
return super(ListField, self).pre_save(model_instance, add)
class SetField(AbstractIterableField):
"""
Field representing a Python ``set``.
"""
_type = set
def get_internal_type(self):
return 'SetField'
def value_to_string(self, obj):
"""
Custom method for serialization, as JSON doesn't support
serializing sets.
"""
return list(self._get_val_from_obj(obj))
class DictField(AbstractIterableField):
"""
Field representing a Python ``dict``.
Type conversions described in :class:`AbstractIterableField` only
affect values of the dictionary, not keys. Depending on the
back-end, keys that aren't strings might not be allowed.
"""
_type = dict
def get_internal_type(self):
return 'DictField'
def _map(self, function, iterable, *args, **kwargs):
return self._type((key, function(value, *args, **kwargs))
for key, value in iterable.iteritems())
def validate(self, values, model_instance):
if not isinstance(values, dict):
raise ValidationError("Value is of type %r. Should be a dict." %
type(values))
class EmbeddedModelField(models.Field):
"""
Field that allows you to embed a model instance.
:param embedded_model: (optional) The model class of instances we
will be embedding; may also be passed as a
string, similar to relation fields
TODO: Make sure to delegate all signals and other field methods to
the embedded instance (not just pre_save, get_db_prep_* and
to_python).
"""
__metaclass__ = models.SubfieldBase
def __init__(self, embedded_model=None, *args, **kwargs):
self.embedded_model = embedded_model
kwargs.setdefault('default', None)
super(EmbeddedModelField, self).__init__(*args, **kwargs)
def get_internal_type(self):
return 'EmbeddedModelField'
def _set_model(self, model):
"""
Resolves embedded model class once the field knows the model it
belongs to.
If the model argument passed to __init__ was a string, we need
to make sure to resolve that string to the corresponding model
class, similar to relation fields.
However, we need to know our own model to generate a valid key
for the embedded model class lookup and EmbeddedModelFields are
not contributed_to_class if used in iterable fields. Thus we
rely on the collection field telling us its model (by setting
our "model" attribute in its contribute_to_class method).
"""
self._model = model
if model is not None and isinstance(self.embedded_model, basestring):
def _resolve_lookup(self_, resolved_model, model):
self.embedded_model = resolved_model
add_lazy_relation(model, self, self.embedded_model, _resolve_lookup)
model = property(lambda self: self._model, _set_model)
def stored_model(self, column_values):
"""
Returns the fixed embedded_model this field was initialized
with (typed embedding) or tries to determine the model from
_module / _model keys stored together with column_values
(untyped embedding).
We give precedence to the field's definition model, as silently
using a differing serialized one could hide some data integrity
problems.
Note that a single untyped EmbeddedModelField may process
instances of different models (especially when used as a type
of a collection field).
"""
module = column_values.pop('_module', None)
model = column_values.pop('_model', None)
if self.embedded_model is not None:
return self.embedded_model
elif module is not None:
return getattr(import_module(module), model)
else:
raise IntegrityError("Untyped EmbeddedModelField trying to load "
"data without serialized model class info.")
def to_python(self, value):
"""
Passes embedded model fields' values through embedded fields
to_python methods and reinstiatates the embedded instance.
We expect to receive a field.attname => value dict together
with a model class from back-end database deconversion (which
needs to know fields of the model beforehand).
"""
# Either the model class has already been determined during
# deconverting values from the database or we've got a dict
# from a deserializer that may contain model class info.
if isinstance(value, tuple):
embedded_model, attribute_values = value
elif isinstance(value, dict):
embedded_model = self.stored_model(value)
attribute_values = value
else:
return value
# Pass values through respective fields' to_python, leaving
# fields for which no value is specified uninitialized.
attribute_values = dict(
(field.attname, field.to_python(attribute_values[field.attname]))
for field in embedded_model._meta.fields
if field.attname in attribute_values)
# Create the model instance.
instance = embedded_model(**attribute_values)
instance._state.adding = False
return instance
def get_db_prep_save(self, embedded_instance, connection):
"""
Applies pre_save and get_db_prep_save of embedded instance
fields and passes a field => value mapping down to database
type conversions.
The embedded instance will be saved as a column => value dict
in the end (possibly augmented with info about instance's model
for untyped embedding), but because we need to apply database
type conversions on embedded instance fields' values and for
these we need to know fields those values come from, we need to
entrust the database layer with creating the dict.
"""
if embedded_instance is None:
return None
# The field's value should be an instance of the model given in
# its declaration or at least of some model.
embedded_model = self.embedded_model or models.Model
if not isinstance(embedded_instance, embedded_model):
raise TypeError("Expected instance of type %r, not %r." %
(embedded_model, type(embedded_instance)))
# Apply pre_save and get_db_prep_save of embedded instance
# fields, create the field => value mapping to be passed to
# storage preprocessing.
field_values = {}
add = embedded_instance._state.adding
for field in embedded_instance._meta.fields:
value = field.get_db_prep_save(
field.pre_save(embedded_instance, add), connection=connection)
# Exclude unset primary keys (e.g. {'id': None}).
if field.primary_key and value is None:
continue
field_values[field] = value
# Let untyped fields store model info alongside values.
# We use fake RawFields for additional values to avoid passing
# embedded_instance to database conversions and to give
# back-ends a chance to apply generic conversions.
if self.embedded_model is None:
module_field = RawField()
module_field.set_attributes_from_name('_module')
model_field = RawField()
model_field.set_attributes_from_name('_model')
field_values.update(
((module_field, embedded_instance.__class__.__module__),
(model_field, embedded_instance.__class__.__name__)))
# This instance will exist in the database soon.
# TODO.XXX: Ensure that this doesn't cause race conditions.
embedded_instance._state.adding = False
return field_values
# TODO/XXX: Remove this once we have a cleaner solution.
def get_db_prep_lookup(self, lookup_type, value, connection,
prepared=False):
if hasattr(value, 'as_lookup_value'):
value = value.as_lookup_value(self, lookup_type, connection)
return value
class BlobField(models.Field):
"""
A field for storing blobs of binary data.
The value might either be a string (or something that can be
converted to a string), or a file-like object.
In the latter case, the object has to provide a ``read`` method
from which the blob is read.
"""
def get_internal_type(self):
return 'BlobField'
def formfield(self, **kwargs):
"""
A file widget is provided, but use model FileField or
ImageField for storing specific files most of the time.
"""
from .widgets import BlobWidget
from django.forms import FileField
defaults = {'form_class': FileField, 'widget': BlobWidget}
defaults.update(kwargs)
return super(BlobField, self).formfield(**defaults)
def get_db_prep_save(self, value, connection):
if hasattr(value, 'read'):
return value.read()
else:
return str(value)
def get_db_prep_lookup(self, lookup_type, value, connection,
prepared=False):
raise TypeError("BlobFields do not support lookups.")
def value_to_string(self, obj):
return str(self._get_val_from_obj(obj))