Ticket #3148: django-3148-approach-via-descriptors.diff

File django-3148-approach-via-descriptors.diff, 5.4 KB (added by Daniel Hahler, 10 years ago)

Approach using FieldDescriptor class (and using decorators to decide if fields get wrapped into it or not)

  • django/db/models/fields/__init__.py

     
    4848#
    4949#     getattr(obj, opts.pk.attname)
    5050
     51class FieldDescriptor(object):
     52    """
     53    This gets added as descriptor for fields that implement
     54    custom getter and/or setter decorators.
     55    """
     56    def __init__(self, field):
     57        self.field = field
     58
     59    def __get__(self, instance, owner):
     60        if instance is None:
     61            return self.field
     62        if "__get__" in self.field._property:
     63            return self.field._property["__get__"](instance)
     64        return instance.__dict__[self.field.name]
     65
     66    def __set__(self, instance, value):
     67        if instance is None:
     68            return self.field
     69        if "__set__" in self.field._property:
     70            return self.field._property["__set__"](instance, value)
     71        instance.__dict__[self.field.name] = value
     72
    5173class Field(object):
    5274    # Designates whether empty strings fundamentally are allowed at the
    5375    # database level.
     
    97119            self.creation_counter = Field.creation_counter
    98120            Field.creation_counter += 1
    99121
     122        self._property = {} #  stores kwargs for descriptors
     123
    100124    def __cmp__(self, other):
    101125        # This is needed because bisect does not take a comparison function.
    102126        return cmp(self.creation_counter, other.creation_counter)
     
    148172        return self._unique or self.primary_key
    149173    unique = property(unique)
    150174
     175    def setter(self, f):
     176        self._property['__set__'] = f
     177        return f
     178
     179    def getter(self, f):
     180        self._property['__get__'] = f
     181        return f
     182
     183    def deleter(self, f):
     184        self._property['__delete__'] = f
     185        return f
     186
    151187    def set_attributes_from_name(self, name):
    152188        self.name = name
    153189        self.attname, self.column = self.get_attname_column()
     
    160196        if self.choices:
    161197            setattr(cls, 'get_%s_display' % self.name, curry(cls._get_FIELD_display, field=self))
    162198
     199        if self._property:
     200            setattr(cls, name, FieldDescriptor(self))
     201        else:
     202            setattr(cls, name, self)
     203
    163204    def get_attname(self):
    164205        return self.name
    165206
  • tests/regressiontests/model_fields/tests.py

     
    77from django.core.exceptions import ValidationError
    88
    99from models import Foo, Bar, Whiz, BigD, BigS, Image
     10from models import FooGetSet
    1011
    1112try:
    1213    from decimal import Decimal
     
    144145        bs = BigS.objects.create(s = 'slug'*50)
    145146        bs = BigS.objects.get(pk=bs.pk)
    146147        self.assertEqual(bs.s, 'slug'*50)
     148
     149class FieldProperties(django.test.TestCase):
     150    def test_get_set(self):
     151        """
     152        Test basic getter and setter functionality of a SlugField
     153        """
     154        f = FooGetSet( s = "test" )
     155        self.assertEqual(f.s, "test")
     156        f.s = "test2"
     157        self.assertEqual(f.s, "test2")
     158
     159        def set_non_acceptable():
     160            f.s = "not_acceptable"
     161        self.assertRaises(ValueError, set_non_acceptable)
     162        self.assertEqual(f.s, "test2")
     163
     164        f.save()
     165        f = FooGetSet.objects.get(pk=f.pk)
     166        self.assertEqual(f.s, "test2")
     167
     168        self.assertEqual(f.file.__class__.__name__, "FieldFile")
     169
     170        # TODO: test that this does not go through FieldDescriptor
     171        #self.assertEqual(f.b, '')
     172        #self.assertFalse(hasattr(FooGetSet.b, '__get__'))
     173        #self.assertTrue(hasattr(f.s, '__get__'))
     174
     175    def test_only_setter(self):
     176        f = FooGetSet(only_setter = 1)
     177        self.assertEqual(f.only_setter, 1)
     178        f.only_setter = 10
     179        self.assertEqual(f.only_setter, 10)
     180
     181
  • tests/regressiontests/model_fields/models.py

     
    1717from django.db import models
    1818from django.db.models.fields.files import ImageFieldFile, ImageField
    1919
     20temp_storage_location = tempfile.mkdtemp()
     21temp_storage = FileSystemStorage(location=temp_storage_location)
    2022
    2123class Foo(models.Model):
    2224    a = models.CharField(max_length=10)
     
    5153class BigS(models.Model):
    5254    s = models.SlugField(max_length=255)
    5355
     56class FooGetSet(models.Model):
     57    s = models.SlugField(max_length=255)
     58    file = models.FileField(storage=temp_storage, upload_to='tests')
     59    b = models.CharField(max_length=10)
     60    only_setter = models.CharField(max_length=10)
    5461
     62    @s.getter
     63    def get_s(self):
     64        return self._s
     65    @s.setter
     66    def set_s(self, value):
     67        if value is "not_acceptable":
     68            raise ValueError("not acceptable")
     69        self._s = value
     70
     71    @only_setter.setter
     72    def set_only_setter(self, value):
     73        self.set_through_setter = value
     74        self.__dict__['only_setter'] = value
     75
     76    @file.getter
     77    def get_file(self):
     78        assert False
     79    @file.setter
     80    def set_file(self, value):
     81        assert False
     82
    5583###############################################################################
    5684# ImageField
    5785
Back to Top