Unnamed
Posted by Anonymous
in response to Unnamed
highlighted in
Python
- ActionScript
- ActionScript 3
- Bash
- Brainfuck
- C
- C#
- C++
- CSS
- Diff
- Django/Jinja
- ERB
- Erlang
- Genshi
- Genshi Text
- Gettext Catalog
- HTML
- INI
- Java
- JavaScript
- Lua
- Mako
- Myghty
- MySQL
- Objective-C
- Perl
- PHP
- Plaintext
- Python
- Python 3
- Python console session
- Python Traceback
- RHTML
- Ruby
- Ruby irb session
- Smarty
- SQL
- VB.net
- XML
- XSLT
"""
Read the class notes for more information.
Example usage:
class Source(TemplateModel):
class_name = models.CharField(...)
class FeedExtension(Source): pass
class TwitterExtension(Source): pass
Source.objects.create(class_name='FeedExtension')
<FeedExtension: FeedExtension object>
Source._children
{
'FeedExtension': <class 'FeedExtension'>,
'TwitterExtension': <class 'TwitterExtension'>
}
"""
from django.db.models.base import *
class TemplateModelBase(ModelBase):
def __new__(cls, name, bases, attrs):
"""
Here is where the magic happens. When the class is instantiated we dig
down to find the base template class (which typically is the originating parent).
We then append all subclasses into the ._children attribute on that
parent class.
"""
parents = [b for b in bases if isinstance(b, TemplateModelBase)]
if not parents:
# If this isn't a subclass of Model, don't do anything special.
return super(ModelBase, cls).__new__(cls, name, bases, attrs)
if parents[0] != TemplateModel:
if 'Meta' not in attrs:
class Meta:
abstract = True
else:
attrs['Meta'].abstract = True
abstract = True
else:
abstract = False
cls = cls._new_cls(name, bases, attrs)
if abstract:
parents = cls.__bases__
while parents[0] != TemplateModel:
parent = parents[0]
parents = parent.__bases__
parent._children[cls.__name__] = cls
cls.__template_key__ = parent.__template_key__
cls._parent = parent
else:
if not hasattr(cls, '_children'):
cls._children = {}
return cls
def __call__(cls, *args, **kwargs):
"""
When the class is called, we lookup the field name which is going to be called
and attempt to extrapolate if that key has been included in instantiation. If
it has we go ahead and return the child class instead of the parent class.
"""
field = cls._meta.get_field_by_name(cls.__template_key__)[0]
position = cls._meta.fields.index(field)
if len(args) > position:
# if it's in the args, we can get it easily by index
result = args[position]
elif field.attname in kwargs:
# retrieve the pk value. Note that we use attname instead of name, to handle the case where the pk is a
# a ForeignKey.
result = kwargs[field.attname]
elif field.name != field.attname and field.name in kwargs:
# ok we couldn't find the value, but maybe it's a FK and we can find the corresponding object instead
result = kwargs[field.name]
else:
raise NotImplementedError
if hasattr(cls, '_children'):
cls = cls._children[result]
return super(TemplateModelBase, cls).__call__(*args, **kwargs)
def _new_cls(cls, name, bases, attrs):
"""
This is almost identical to ModelBase.__new__. Modifications
are simply to remove some of the code which deals with models
being abstract=True.
"""
parents = [b for b in bases if isinstance(b, TemplateModelBase)]
# Create the class.
module = attrs.pop('__module__')
new_class = super(ModelBase, cls).__new__(cls, name, bases, {'__module__': module})
attr_meta = attrs.pop('Meta', None)
abstract = getattr(attr_meta, 'abstract', False)
if not attr_meta:
meta = getattr(new_class, 'Meta', None)
else:
meta = attr_meta
base_meta = getattr(new_class, '_meta', None)
if getattr(meta, 'app_label', None) is None:
# Figure out the app_label by looking one level up.
# For 'django.contrib.sites.models', this would be 'sites'.
model_module = sys.modules[new_class.__module__]
kwargs = {"app_label": model_module.__name__.split('.')[-2]}
else:
kwargs = {}
new_class.add_to_class('_meta', Options(meta, **kwargs))
if not abstract:
new_class.add_to_class('DoesNotExist',
subclass_exception('DoesNotExist', ObjectDoesNotExist, module))
new_class.add_to_class('MultipleObjectsReturned',
subclass_exception('MultipleObjectsReturned', MultipleObjectsReturned, module))
if base_meta and not base_meta.abstract:
# Non-abstract child classes inherit some attributes from their
# non-abstract parent (unless an ABC comes before it in the
# method resolution order).
if not hasattr(meta, 'ordering'):
new_class._meta.ordering = base_meta.ordering
if not hasattr(meta, 'get_latest_by'):
new_class._meta.get_latest_by = base_meta.get_latest_by
if getattr(new_class, '_default_manager', None):
new_class._default_manager = None
# Bail out early if we have already created this class.
m = get_model(new_class._meta.app_label, name, False)
if m is not None:
return m
# Add all attributes to the class.
for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj)
# Do the appropriate setup for any model parents.
for base in parents:
if not hasattr(base, '_meta'):
# Things without _meta aren't functional models, so they're
# uninteresting parents.
continue
# All the fields of any type declared on this model
new_fields = new_class._meta.local_fields + \
new_class._meta.local_many_to_many + \
new_class._meta.virtual_fields
field_names = set([f.name for f in new_fields])
parent_fields = base._meta.local_fields + base._meta.local_many_to_many
for field in parent_fields:
if field.name in field_names:
raise FieldError('Local field %r in class %r clashes '\
'with field of similar name from '\
'abstract base class %r' % \
(field.name, name, base.__name__))
new_class.add_to_class(field.name, copy.deepcopy(field))
# Inherit managers from the abstract base classes.
base_managers = base._meta.abstract_managers
base_managers.sort()
for _, mgr_name, manager in base_managers:
val = getattr(new_class, mgr_name, None)
if not val or val is manager:
new_manager = manager._copy_to_model(new_class)
new_class.add_to_class(mgr_name, new_manager)
# Inherit virtual fields (like GenericForeignKey) from the parent class
for field in base._meta.virtual_fields:
if base._meta.abstract and field.name in field_names:
raise FieldError('Local field %r in class %r clashes '\
'with field of similar name from '\
'abstract base class %r' % \
(field.name, name, base.__name__))
new_class.add_to_class(field.name, copy.deepcopy(field))
if abstract:
# Abstract base models can't be instantiated and don't appear in
# the list of models for an app. We do the final setup for them a
# little differently from normal models.
attr_meta.abstract = False
new_class.Meta = attr_meta
return new_class
new_class._prepare()
register_models(new_class._meta.app_label, new_class)
# Because of the way imports happen (recursively), we may or may not be
# the first time this model tries to register with the framework. There
# should only be one class for each model, so we always return the
# registered version.
return get_model(new_class._meta.app_label, name, False)
_new_cls = classmethod(_new_cls)
class TemplateModel(Model):
"""
The TemplateModel is designed to allow a sort-of inheritance. You may specify a base
model, and inherit from it. This base is the only table created in the database, and
must contain a class_name field (specifiable with __template_key__). Now whenever the
model class is instantiated, it will automatically be replaced by the child class
which inherited from it.
"""
__metaclass__ = TemplateModelBase
# The field name which should be used for storage of the subclasses name.
__template_key__ = 'class_name'
def _get_pk_val(self, meta):
if hasattr(self, '_parent'):
meta = self._parent._meta
return Model._get_pk_val(self, meta)
def save(self, force_insert=False, force_update=False):
"""
To handle saving of a child class, we need to force the save method to
be called on the parent table.
"""
if force_insert and force_update:
raise ValueError("Cannot force both insert and updating in "
"model saving.")
self.save_base(cls=getattr(self, '_parent', self), force_insert=force_insert, force_update=force_update)