Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added the small changes necessary to make creating custom model field…

…s easier.

Also includes some tests for this.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@6651 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit ea100b607acbca31e813118d84c5c6c48fda1ae0 1 parent 595e75e
Malcolm Tredinnick malcolmt authored
1  django/db/models/__init__.py
View
@@ -7,6 +7,7 @@
from django.db.models.manager import Manager
from django.db.models.base import Model, AdminOptions
from django.db.models.fields import *
+from django.db.models.fields.subclassing import SubfieldBase
from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED
from django.db.models import signals
from django.utils.functional import curry
2  django/db/models/fields/__init__.py
View
@@ -147,6 +147,8 @@ def db_type(self):
# exactly which wacky database column type you want to use.
data_types = get_creation_module().DATA_TYPES
internal_type = self.get_internal_type()
+ if internal_type not in data_types:
+ return None
return data_types[internal_type] % self.__dict__
def validate_full(self, field_data, all_data):
53 django/db/models/fields/subclassing.py
View
@@ -0,0 +1,53 @@
+"""
+Convenience routines for creating non-trivial Field subclasses.
+
+Add SubfieldBase as the __metaclass__ for your Field subclass, implement
+to_python() and the other necessary methods and everything will work seamlessly.
+"""
+
+from django.utils.maxlength import LegacyMaxlength
+
+class SubfieldBase(LegacyMaxlength):
+ """
+ A metaclass for custom Field subclasses. This ensures the model's attribute
+ has the descriptor protocol attached to it.
+ """
+ def __new__(cls, base, name, attrs):
+ new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs)
+ new_class.contribute_to_class = make_contrib(
+ attrs.get('contribute_to_class'))
+ return new_class
+
+class Creator(object):
+ """
+ A placeholder class that provides a way to set the attribute on the model.
+ """
+ def __init__(self, field):
+ self.field = field
+
+ def __get__(self, obj, type=None):
+ if obj is None:
+ raise AttributeError('Can only be accessed via an instance.')
+ return self.value
+
+ def __set__(self, obj, value):
+ self.value = self.field.to_python(value)
+
+def make_contrib(func=None):
+ """
+ Returns a suitable contribute_to_class() method for the Field subclass.
+
+ If 'func' is passed in, it is the existing contribute_to_class() method on
+ the subclass and it is called before anything else. It is assumed in this
+ case that the existing contribute_to_class() calls all the necessary
+ superclass methods.
+ """
+ def contribute_to_class(self, cls, name):
+ if func:
+ func(self, cls, name)
+ else:
+ super(self.__class__, self).contribute_to_class(cls, name)
+ setattr(cls, self.name, Creator(self))
+
+ return contribute_to_class
+
0  tests/modeltests/field_subclassing/__init__.py
View
No changes.
106 tests/modeltests/field_subclassing/models.py
View
@@ -0,0 +1,106 @@
+"""
+Tests for field subclassing.
+"""
+
+from django.db import models
+from django.utils.encoding import force_unicode
+from django.core import serializers
+
+class Small(object):
+ """
+ A simple class to show that non-trivial Python objects can be used as
+ attributes.
+ """
+ def __init__(self, first, second):
+ self.first, self.second = first, second
+
+ def __unicode__(self):
+ return u'%s%s' % (force_unicode(self.first), force_unicode(self.second))
+
+ def __str__(self):
+ return unicode(self).encode('utf-8')
+
+class SmallField(models.Field):
+ """
+ Turns the "Small" class into a Django field. Because of the similarities
+ with normal character fields and the fact that Small.__unicode__ does
+ something sensible, we don't need to implement a lot here.
+ """
+ __metaclass__ = models.SubfieldBase
+
+ def __init__(self, *args, **kwargs):
+ kwargs['max_length'] = 2
+ super(SmallField, self).__init__(*args, **kwargs)
+
+ def get_internal_type(self):
+ return 'CharField'
+
+ def to_python(self, value):
+ if isinstance(value, Small):
+ return value
+ return Small(value[0], value[1])
+
+ def get_db_prep_save(self, value):
+ return unicode(value)
+
+ def get_db_prep_lookup(self, lookup_type, value):
+ if lookup_type == 'exact':
+ return force_unicode(value)
+ if lookup_type == 'in':
+ return [force_unicode(v) for v in value]
+ if lookup_type == 'isnull':
+ return []
+ raise TypeError('Invalid lookup type: %r' % lookup_type)
+
+ def flatten_data(self, follow, obj=None):
+ return {self.attname: force_unicode(self._get_val_from_obj(obj))}
+
+class MyModel(models.Model):
+ name = models.CharField(max_length=10)
+ data = SmallField('small field')
+
+ def __unicode__(self):
+ return force_unicode(self.name)
+
+__test__ = {'API_TESTS': ur"""
+# Creating a model with custom fields is done as per normal.
+>>> s = Small(1, 2)
+>>> print s
+12
+>>> m = MyModel(name='m', data=s)
+>>> m.save()
+
+# Custom fields still have normal field's attributes.
+>>> m._meta.get_field('data').verbose_name
+'small field'
+
+# The m.data attribute has been initialised correctly. It's a Small object.
+>>> m.data.first, m.data.second
+(1, 2)
+
+# The data loads back from the database correctly and 'data' has the right type.
+>>> m1 = MyModel.objects.get(pk=m.pk)
+>>> isinstance(m1.data, Small)
+True
+>>> print m1.data
+12
+
+# We can do normal filtering on the custom field (and will get an error when we
+# use a lookup type that does not make sense).
+>>> s1 = Small(1, 3)
+>>> s2 = Small('a', 'b')
+>>> MyModel.objects.filter(data__in=[s, s1, s2])
+[<MyModel: m>]
+>>> MyModel.objects.filter(data__lt=s)
+Traceback (most recent call last):
+...
+TypeError: Invalid lookup type: 'lt'
+
+# Serialization works, too.
+>>> stream = serializers.serialize("json", MyModel.objects.all())
+>>> stream
+'[{"pk": 1, "model": "field_subclassing.mymodel", "fields": {"data": "12", "name": "m"}}]'
+>>> obj = list(serializers.deserialize("json", stream))[0]
+>>> obj.object == m
+True
+"""}
Please sign in to comment.
Something went wrong with that request. Please try again.