Refactored metaclass in preparation for descriptor fields

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@1701 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robert Wittams 2005-12-16 19:52:06 +00:00
parent 43b41a7565
commit f4dfb99f45
5 changed files with 123 additions and 88 deletions

View File

@ -17,7 +17,6 @@ from django.db.models.fields.related import *
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.exceptions import FieldDoesNotExist, BadKeywordArguments
# Admin stages.
ADD, CHANGE, BOTH = 1, 2, 3
@ -28,7 +27,6 @@ ADD, CHANGE, BOTH = 1, 2, 3
#def get_app(app_label):
# return __import__('%s.%s' % (MODEL_PREFIX, app_label), '', '', [''])
class LazyDate:
"""
Use in limit_choices_to to compare the field to dates calculated at run time
@ -50,11 +48,6 @@ class LazyDate:
def __get_value__(self):
return datetime.datetime.now() + self.delta
################
# MAIN CLASSES #
################

View File

@ -1,6 +1,6 @@
from django.db.models.manipulators import ManipulatorDescriptor, ModelAddManipulator, ModelChangeManipulator
from django.db.models.fields import Field, DateField, FileField, ImageField, AutoField
from django.db.models.fields.related import OneToOne, ManyToOne, ManyToMany, RECURSIVE_RELATIONSHIP_CONSTANT
from django.db.models.fields.related import RelatedField, OneToOne, ManyToOne, ManyToMany, RECURSIVE_RELATIONSHIP_CONSTANT
from django.db.models.related import RelatedObject
from django.db.models.manager import Manager, ManagerDescriptor
from django.db.models.query import orderlist2sql
@ -36,23 +36,9 @@ class ModelBase(type):
except KeyError:
meta_attrs = {}
# Gather all attributes that are Field or Manager instances.
fields, managers = [], []
for obj_name, obj in attrs.items():
if isinstance(obj, Field):
obj.set_name(obj_name)
fields.append(obj)
del attrs[obj_name]
elif isinstance(obj, Manager):
managers.append((obj_name, obj))
del attrs[obj_name]
# Sort the fields and managers in the order that they were created. The
# "creation_counter" is needed because metaclasses don't preserve the
# attribute order.
fields.sort(lambda x, y: x.creation_counter - y.creation_counter)
managers.sort(lambda x, y: x[1].creation_counter - y[1].creation_counter)
# Create the class, because we need it to use in currying.
new_class = type.__new__(cls, name, bases, { '__module__' : attrs.pop('__module__') })
opts = Options(
module_name = meta_attrs.pop('module_name', get_module_name(name)),
# If the verbose_name wasn't given, use the class name,
@ -60,7 +46,6 @@ class ModelBase(type):
verbose_name = meta_attrs.pop('verbose_name', get_verbose_name(name)),
verbose_name_plural = meta_attrs.pop('verbose_name_plural', ''),
db_table = meta_attrs.pop('db_table', ''),
fields = fields,
ordering = meta_attrs.pop('ordering', None),
unique_together = meta_attrs.pop('unique_together', None),
admin = meta_attrs.pop('admin', None),
@ -76,38 +61,24 @@ class ModelBase(type):
if meta_attrs != {}:
raise TypeError, "'class META' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
new_class.add_to_class('_meta', opts)
# Create the DoesNotExist exception.
attrs['DoesNotExist'] = types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {})
# Create the class, because we need it to use in currying.
new_class = type.__new__(cls, name, bases, attrs)
# Give the class a docstring -- its definition.
if new_class.__doc__ is None:
new_class.__doc__ = "%s.%s(%s)" % (opts.module_name, name, ", ".join([f.name for f in opts.fields]))
if hasattr(new_class, 'get_absolute_url'):
new_class.get_absolute_url = curry(get_absolute_url, opts, new_class.get_absolute_url)
new_class.DoesNotExist = types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {})
# Figure out the app_label by looking one level up.
#FIXME: wrong for nested model modules
app_package = sys.modules.get(new_class.__module__)
app_label = app_package.__name__.replace('.models', '')
app_label = app_label[app_label.rfind('.')+1:]
# Populate the _MODELS member on the module the class is in.
app_package.__dict__.setdefault('_MODELS', []).append(new_class)
# Cache the app label.
opts.app_label = app_label
# If the db_table wasn't provided, use the app_label + module_name.
if not opts.db_table:
opts.db_table = "%s_%s" % (app_label, opts.module_name)
new_class._meta = opts
for m_name, m in managers:
new_class.add_to_class(m_name, m)
#Add all attributes to the class
#fields, managers = [], []
for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj)
if not hasattr(new_class, '_default_manager'):
# Create the default manager, if needed.
@ -115,18 +86,25 @@ class ModelBase(type):
raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % name
new_class.add_to_class('objects', Manager())
new_class._prepare()
for field in fields:
if field.rel:
other = field.rel.to
if isinstance(other, basestring):
print "string lookup"
else:
related = RelatedObject(other._meta, new_class, field)
field.contribute_to_related_class(other, related)
# Give the class a docstring -- its definition.
if new_class.__doc__ is None:
new_class.__doc__ = "%s.%s(%s)" % (opts.module_name, name, ", ".join([f.name for f in opts.fields]))
if hasattr(new_class, 'get_absolute_url'):
new_class.get_absolute_url = curry(get_absolute_url, opts, new_class.get_absolute_url)
opts._prepare()
new_class._prepare()
# If the db_table wasn't provided, use the app_label + module_name.
if not opts.db_table:
opts.db_table = "%s_%s" % (app_label, opts.module_name)
# Populate the _MODELS member on the module the class is in.
app_package.__dict__.setdefault('_MODELS', []).append(new_class)
return new_class
@ -207,13 +185,6 @@ class Model(object):
if not f.height_field:
setattr(cls, 'get_%s_height' % f.name, curry(cls.__get_FIELD_height, field=f))
# If the object has a relationship to itself, as designated by
# RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally.
if f.rel and f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT:
f.rel.to = cls
f.name = f.name or (f.rel.to._meta.object_name.lower() + '_' + f.rel.to._meta.pk.name)
f.verbose_name = f.verbose_name or f.rel.to._meta.verbose_name
f.rel.field_name = f.rel.field_name or f.rel.to._meta.pk.name
# Add methods for many-to-one related objects.
# EXAMPLES: Choice.get_poll(), Story.get_dateline()
@ -233,6 +204,8 @@ class Model(object):
if cls._meta.order_with_respect_to:
cls.get_next_in_order = curry(cls.__get_next_or_previous_in_order, is_next=True)
cls.get_previous_in_order = curry(cls.__get_next_or_previous_in_order, is_next=False)
RelatedField.do_pending_lookups(cls)
_prepare = classmethod(_prepare)

View File

@ -88,7 +88,7 @@ class Field(object):
# Tracks each time a Field instance is created. Used to retain order.
creation_counter = 0
def __init__(self, verbose_name=None, name=None, primary_key=False,
maxlength=None, unique=False, blank=False, null=False, db_index=False,
core=False, rel=None, default=NOT_PROVIDED, editable=True,
@ -96,7 +96,8 @@ class Field(object):
unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
help_text='', db_column=None):
self.name = name
self.verbose_name = verbose_name or (name and name.replace('_', ' '))
self.verbose_name = verbose_name
self.primary_key = primary_key
self.maxlength, self.unique = maxlength, unique
self.blank, self.null = blank, null
@ -110,16 +111,23 @@ class Field(object):
self.radio_admin = radio_admin
self.help_text = help_text
self.db_column = db_column
# Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
# Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
self.db_index = db_index
# Increase the creation counter, and save our local copy.
self.creation_counter = Field.creation_counter
Field.creation_counter += 1
def __cmp__(self,other ):
#This is because bisect does not take a comparison function. grrr.
return cmp(self.creation_counter, other.creation_counter)
def contribute_to_class(self, cls, name):
self.name = name
self.attname, self.column = self.get_attname_column()
self.verbose_name = self.verbose_name or (name and name.replace('_', ' '))
cls._meta.add_field(self)
def set_name(self, name):
self.name = name

View File

@ -1,21 +1,63 @@
from django.db.models.fields import Field, IntegerField
from django.db.models.related import RelatedObject
from django.utils.translation import gettext_lazy, string_concat
from django.utils.functional import curry
from django.core import formfields
# Values for Relation.edit_inline.
TABULAR, STACKED = 1, 2
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
#HACK
class RelatedField(object):
pending_lookups = {}
def add_lookup(cls, rel_cls, field):
name = field.rel.to
module = rel_cls.__module__
key = (module, name)
cls.pending_lookups.setdefault(key,[]).append( (rel_cls, field) )
add_lookup = classmethod(add_lookup)
def do_pending_lookups(cls, other_cls):
key = (other_cls.__module__, other_cls.__name__)
for (rel_cls,field) in cls.pending_lookups.setdefault(key,[]):
field.rel.to = other_cls
field.do_related_class(other_cls, rel_cls)
do_pending_lookups = classmethod(do_pending_lookups)
def contribute_to_class(self, cls, name):
Field.contribute_to_class(self,cls,name)
other = self.rel.to
if isinstance(other, basestring):
if other == RECURSIVE_RELATIONSHIP_CONSTANT:
self.rel.to = cls.__name__
self.add_lookup(cls, self)
else:
self.do_related_class(other, cls)
def set_attributes_from_rel(self):
self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name)
self.verbose_name = self.verbose_name or self.rel.to._meta.verbose_name
self.rel.field_name = self.rel.field_name or self.rel.to._meta.pk.name
def do_related_class(self, other, cls):
self.set_attributes_from_rel()
related = RelatedObject(other._meta, cls, self)
self.contribute_to_related_class(other, related)
#HACK
class SharedMethods(object):
class SharedMethods(RelatedField):
def get_attname(self):
return '%s_id' % self.name
def get_validator_unique_lookup_type(self):
return '%s__%s__exact' % (self.name, self.rel.get_related_field().name)
class ForeignKey(SharedMethods,Field):
empty_strings_allowed = False
def __init__(self, to, to_field=None, **kwargs):
@ -98,7 +140,6 @@ class ForeignKey(SharedMethods,Field):
def contribute_to_related_class(self, cls, related):
rel_obj_name = related.get_method_name_part()
# Add "get_thingie" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice()
setattr(cls, 'get_%s' % rel_obj_name, curry(cls._get_related, method_name='get_object', rel_class=related.model, rel_field=related.field))
@ -147,7 +188,7 @@ class OneToOneField(SharedMethods, IntegerField):
rel_class=related.model, rel_field=related.field))
class ManyToManyField(Field):
class ManyToManyField(RelatedField,Field):
def __init__(self, to, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
kwargs['rel'] = ManyToMany(to, kwargs.pop('singular', None),
@ -225,6 +266,9 @@ class ManyToManyField(Field):
func.alters_data = True
setattr(cls, 'set_%s' % related.opts.module_name, func)
def set_attributes_from_rel(self):
pass
class ManyToOne:
def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,

View File

@ -5,19 +5,17 @@ from django.db.models.loading import get_installed_model_modules
from django.db.models.query import orderlist2sql
from django.db.models.exceptions import FieldDoesNotExist
from bisect import bisect
class Options:
def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='',
fields=None, ordering=None, unique_together=None, admin=None,
ordering=None, unique_together=None, admin=None,
where_constraints=None, object_name=None, app_label=None,
exceptions=None, permissions=None, get_latest_by=None,
order_with_respect_to=None, module_constants=None):
# Move many-to-many related fields from self.fields into self.many_to_many.
self.fields, self.many_to_many = [], []
for field in (fields or []):
if field.rel and isinstance(field.rel, ManyToMany):
self.many_to_many.append(field)
else:
self.fields.append(field)
self.module_name, self.verbose_name = module_name, verbose_name
self.verbose_name_plural = verbose_name_plural or verbose_name + 's'
self.db_table = db_table
@ -28,14 +26,22 @@ class Options:
self.permissions = permissions or []
self.object_name, self.app_label = object_name, app_label
self.get_latest_by = get_latest_by
if order_with_respect_to:
self.order_with_respect_to = order_with_respect_to
self.module_constants = module_constants or {}
self.admin = admin
def contribute_to_class(self, cls, name):
self.model = cls
cls._meta = self
def _prepare(self):
if self.order_with_respect_to:
self.order_with_respect_to = self.get_field(order_with_respect_to)
self.ordering = ('_order',)
else:
self.order_with_respect_to = None
self.module_constants = module_constants or {}
self.admin = admin
# Calculate one_to_one_field.
self.one_to_one_field = None
for f in self.fields:
@ -51,7 +57,9 @@ class Options:
# If a primary_key field hasn't been specified, add an
# auto-incrementing primary-key ID field automatically.
if self.pk is None:
self.fields.insert(0, AutoField(name='id', verbose_name='ID', primary_key=True))
auto = AutoField(verbose_name='ID', primary_key=True)
auto.creation_counter = -1
self.model.add_to_class('id', auto)
self.pk = self.fields[0]
# Cache whether this has an AutoField.
self.has_auto_field = False
@ -64,6 +72,15 @@ class Options:
#HACK
self.limit_choices_to = {}
def add_field(self, field):
# Insert the fields in the order that they were created. The
# "creation_counter" is needed because metaclasses don't preserve the
# attribute order.
if field.rel and isinstance(field.rel, ManyToMany):
self.many_to_many.insert(bisect(self.many_to_many, field), field)
else:
self.fields.insert(bisect(self.fields,field),field)
def __repr__(self):
return '<Options for %s>' % self.module_name