web/lib/django/db/models/fields/subclassing.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 """
       
     2 Convenience routines for creating non-trivial Field subclasses, as well as
       
     3 backwards compatibility utilities.
       
     4 
       
     5 Add SubfieldBase as the __metaclass__ for your Field subclass, implement
       
     6 to_python() and the other necessary methods and everything will work seamlessly.
       
     7 """
       
     8 
       
     9 from inspect import getargspec
       
    10 from warnings import warn
       
    11 
       
    12 def call_with_connection(func):
       
    13     arg_names, varargs, varkwargs, defaults = getargspec(func)
       
    14     updated = ('connection' in arg_names or varkwargs)
       
    15     if not updated:
       
    16         warn("A Field class whose %s method hasn't been updated to take a "
       
    17             "`connection` argument." % func.__name__,
       
    18             PendingDeprecationWarning, stacklevel=2)
       
    19 
       
    20     def inner(*args, **kwargs):
       
    21         if 'connection' not in kwargs:
       
    22             from django.db import connection
       
    23             kwargs['connection'] = connection
       
    24             warn("%s has been called without providing a connection argument. " %
       
    25                 func.__name__, PendingDeprecationWarning,
       
    26                 stacklevel=1)
       
    27         if updated:
       
    28             return func(*args, **kwargs)
       
    29         if 'connection' in kwargs:
       
    30             del kwargs['connection']
       
    31         return func(*args, **kwargs)
       
    32     return inner
       
    33 
       
    34 def call_with_connection_and_prepared(func):
       
    35     arg_names, varargs, varkwargs, defaults = getargspec(func)
       
    36     updated = (
       
    37         ('connection' in arg_names or varkwargs) and
       
    38         ('prepared' in arg_names or varkwargs)
       
    39     )
       
    40     if not updated:
       
    41         warn("A Field class whose %s method hasn't been updated to take "
       
    42             "`connection` and `prepared` arguments." % func.__name__,
       
    43             PendingDeprecationWarning, stacklevel=2)
       
    44 
       
    45     def inner(*args, **kwargs):
       
    46         if 'connection' not in kwargs:
       
    47             from django.db import connection
       
    48             kwargs['connection'] = connection
       
    49             warn("%s has been called without providing a connection argument. " %
       
    50                 func.__name__, PendingDeprecationWarning,
       
    51                 stacklevel=1)
       
    52         if updated:
       
    53             return func(*args, **kwargs)
       
    54         if 'connection' in kwargs:
       
    55             del kwargs['connection']
       
    56         if 'prepared' in kwargs:
       
    57             del kwargs['prepared']
       
    58         return func(*args, **kwargs)
       
    59     return inner
       
    60 
       
    61 class LegacyConnection(type):
       
    62     """
       
    63     A metaclass to normalize arguments give to the get_db_prep_* and db_type
       
    64     methods on fields.
       
    65     """
       
    66     def __new__(cls, names, bases, attrs):
       
    67         new_cls = super(LegacyConnection, cls).__new__(cls, names, bases, attrs)
       
    68         for attr in ('db_type', 'get_db_prep_save'):
       
    69             setattr(new_cls, attr, call_with_connection(getattr(new_cls, attr)))
       
    70         for attr in ('get_db_prep_lookup', 'get_db_prep_value'):
       
    71             setattr(new_cls, attr, call_with_connection_and_prepared(getattr(new_cls, attr)))
       
    72         return new_cls
       
    73 
       
    74 class SubfieldBase(LegacyConnection):
       
    75     """
       
    76     A metaclass for custom Field subclasses. This ensures the model's attribute
       
    77     has the descriptor protocol attached to it.
       
    78     """
       
    79     def __new__(cls, base, name, attrs):
       
    80         new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs)
       
    81         new_class.contribute_to_class = make_contrib(
       
    82                 attrs.get('contribute_to_class'))
       
    83         return new_class
       
    84 
       
    85 class Creator(object):
       
    86     """
       
    87     A placeholder class that provides a way to set the attribute on the model.
       
    88     """
       
    89     def __init__(self, field):
       
    90         self.field = field
       
    91 
       
    92     def __get__(self, obj, type=None):
       
    93         if obj is None:
       
    94             raise AttributeError('Can only be accessed via an instance.')
       
    95         return obj.__dict__[self.field.name]
       
    96 
       
    97     def __set__(self, obj, value):
       
    98         obj.__dict__[self.field.name] = self.field.to_python(value)
       
    99 
       
   100 def make_contrib(func=None):
       
   101     """
       
   102     Returns a suitable contribute_to_class() method for the Field subclass.
       
   103 
       
   104     If 'func' is passed in, it is the existing contribute_to_class() method on
       
   105     the subclass and it is called before anything else. It is assumed in this
       
   106     case that the existing contribute_to_class() calls all the necessary
       
   107     superclass methods.
       
   108     """
       
   109     def contribute_to_class(self, cls, name):
       
   110         if func:
       
   111             func(self, cls, name)
       
   112         else:
       
   113             super(self.__class__, self).contribute_to_class(cls, name)
       
   114         setattr(cls, self.name, Creator(self))
       
   115 
       
   116     return contribute_to_class