web/lib/django/db/models/fields/subclassing.py
changeset 38 77b6da96e6f1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django/db/models/fields/subclassing.py	Wed Jun 02 18:57:35 2010 +0200
@@ -0,0 +1,116 @@
+"""
+Convenience routines for creating non-trivial Field subclasses, as well as
+backwards compatibility utilities.
+
+Add SubfieldBase as the __metaclass__ for your Field subclass, implement
+to_python() and the other necessary methods and everything will work seamlessly.
+"""
+
+from inspect import getargspec
+from warnings import warn
+
+def call_with_connection(func):
+    arg_names, varargs, varkwargs, defaults = getargspec(func)
+    updated = ('connection' in arg_names or varkwargs)
+    if not updated:
+        warn("A Field class whose %s method hasn't been updated to take a "
+            "`connection` argument." % func.__name__,
+            PendingDeprecationWarning, stacklevel=2)
+
+    def inner(*args, **kwargs):
+        if 'connection' not in kwargs:
+            from django.db import connection
+            kwargs['connection'] = connection
+            warn("%s has been called without providing a connection argument. " %
+                func.__name__, PendingDeprecationWarning,
+                stacklevel=1)
+        if updated:
+            return func(*args, **kwargs)
+        if 'connection' in kwargs:
+            del kwargs['connection']
+        return func(*args, **kwargs)
+    return inner
+
+def call_with_connection_and_prepared(func):
+    arg_names, varargs, varkwargs, defaults = getargspec(func)
+    updated = (
+        ('connection' in arg_names or varkwargs) and
+        ('prepared' in arg_names or varkwargs)
+    )
+    if not updated:
+        warn("A Field class whose %s method hasn't been updated to take "
+            "`connection` and `prepared` arguments." % func.__name__,
+            PendingDeprecationWarning, stacklevel=2)
+
+    def inner(*args, **kwargs):
+        if 'connection' not in kwargs:
+            from django.db import connection
+            kwargs['connection'] = connection
+            warn("%s has been called without providing a connection argument. " %
+                func.__name__, PendingDeprecationWarning,
+                stacklevel=1)
+        if updated:
+            return func(*args, **kwargs)
+        if 'connection' in kwargs:
+            del kwargs['connection']
+        if 'prepared' in kwargs:
+            del kwargs['prepared']
+        return func(*args, **kwargs)
+    return inner
+
+class LegacyConnection(type):
+    """
+    A metaclass to normalize arguments give to the get_db_prep_* and db_type
+    methods on fields.
+    """
+    def __new__(cls, names, bases, attrs):
+        new_cls = super(LegacyConnection, cls).__new__(cls, names, bases, attrs)
+        for attr in ('db_type', 'get_db_prep_save'):
+            setattr(new_cls, attr, call_with_connection(getattr(new_cls, attr)))
+        for attr in ('get_db_prep_lookup', 'get_db_prep_value'):
+            setattr(new_cls, attr, call_with_connection_and_prepared(getattr(new_cls, attr)))
+        return new_cls
+
+class SubfieldBase(LegacyConnection):
+    """
+    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 obj.__dict__[self.field.name]
+
+    def __set__(self, obj, value):
+        obj.__dict__[self.field.name] = 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