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.core.exceptions import ObjectDoesNotExist
from django.db.models.exceptions import FieldDoesNotExist, BadKeywordArguments from django.db.models.exceptions import FieldDoesNotExist, BadKeywordArguments
# Admin stages. # Admin stages.
ADD, CHANGE, BOTH = 1, 2, 3 ADD, CHANGE, BOTH = 1, 2, 3
@ -28,7 +27,6 @@ ADD, CHANGE, BOTH = 1, 2, 3
#def get_app(app_label): #def get_app(app_label):
# return __import__('%s.%s' % (MODEL_PREFIX, app_label), '', '', ['']) # return __import__('%s.%s' % (MODEL_PREFIX, app_label), '', '', [''])
class LazyDate: class LazyDate:
""" """
Use in limit_choices_to to compare the field to dates calculated at run time 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): def __get_value__(self):
return datetime.datetime.now() + self.delta 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.manipulators import ManipulatorDescriptor, ModelAddManipulator, ModelChangeManipulator
from django.db.models.fields import Field, DateField, FileField, ImageField, AutoField 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.related import RelatedObject
from django.db.models.manager import Manager, ManagerDescriptor from django.db.models.manager import Manager, ManagerDescriptor
from django.db.models.query import orderlist2sql from django.db.models.query import orderlist2sql
@ -36,23 +36,9 @@ class ModelBase(type):
except KeyError: except KeyError:
meta_attrs = {} meta_attrs = {}
# Gather all attributes that are Field or Manager instances. # Create the class, because we need it to use in currying.
fields, managers = [], [] new_class = type.__new__(cls, name, bases, { '__module__' : attrs.pop('__module__') })
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)
opts = Options( opts = Options(
module_name = meta_attrs.pop('module_name', get_module_name(name)), module_name = meta_attrs.pop('module_name', get_module_name(name)),
# If the verbose_name wasn't given, use the class 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 = meta_attrs.pop('verbose_name', get_verbose_name(name)),
verbose_name_plural = meta_attrs.pop('verbose_name_plural', ''), verbose_name_plural = meta_attrs.pop('verbose_name_plural', ''),
db_table = meta_attrs.pop('db_table', ''), db_table = meta_attrs.pop('db_table', ''),
fields = fields,
ordering = meta_attrs.pop('ordering', None), ordering = meta_attrs.pop('ordering', None),
unique_together = meta_attrs.pop('unique_together', None), unique_together = meta_attrs.pop('unique_together', None),
admin = meta_attrs.pop('admin', None), admin = meta_attrs.pop('admin', None),
@ -76,38 +61,24 @@ class ModelBase(type):
if meta_attrs != {}: if meta_attrs != {}:
raise TypeError, "'class META' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()) raise TypeError, "'class META' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
new_class.add_to_class('_meta', opts)
# Create the DoesNotExist exception. # Create the DoesNotExist exception.
attrs['DoesNotExist'] = types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {}) new_class.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)
# Figure out the app_label by looking one level up. # 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_package = sys.modules.get(new_class.__module__)
app_label = app_package.__name__.replace('.models', '') app_label = app_package.__name__.replace('.models', '')
app_label = app_label[app_label.rfind('.')+1:] 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. # Cache the app label.
opts.app_label = app_label opts.app_label = app_label
# If the db_table wasn't provided, use the app_label + module_name. #Add all attributes to the class
if not opts.db_table: #fields, managers = [], []
opts.db_table = "%s_%s" % (app_label, opts.module_name) for obj_name, obj in attrs.items():
new_class._meta = opts new_class.add_to_class(obj_name, obj)
for m_name, m in managers:
new_class.add_to_class(m_name, m)
if not hasattr(new_class, '_default_manager'): if not hasattr(new_class, '_default_manager'):
# Create the default manager, if needed. # 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 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.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 return new_class
@ -207,13 +185,6 @@ class Model(object):
if not f.height_field: if not f.height_field:
setattr(cls, 'get_%s_height' % f.name, curry(cls.__get_FIELD_height, field=f)) 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. # Add methods for many-to-one related objects.
# EXAMPLES: Choice.get_poll(), Story.get_dateline() # EXAMPLES: Choice.get_poll(), Story.get_dateline()
@ -233,6 +204,8 @@ class Model(object):
if cls._meta.order_with_respect_to: 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_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) cls.get_previous_in_order = curry(cls.__get_next_or_previous_in_order, is_next=False)
RelatedField.do_pending_lookups(cls)
_prepare = classmethod(_prepare) _prepare = classmethod(_prepare)

View File

@ -88,7 +88,7 @@ class Field(object):
# Tracks each time a Field instance is created. Used to retain order. # Tracks each time a Field instance is created. Used to retain order.
creation_counter = 0 creation_counter = 0
def __init__(self, verbose_name=None, name=None, primary_key=False, def __init__(self, verbose_name=None, name=None, primary_key=False,
maxlength=None, unique=False, blank=False, null=False, db_index=False, maxlength=None, unique=False, blank=False, null=False, db_index=False,
core=False, rel=None, default=NOT_PROVIDED, editable=True, 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, unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
help_text='', db_column=None): help_text='', db_column=None):
self.name = name self.name = name
self.verbose_name = verbose_name or (name and name.replace('_', ' ')) self.verbose_name = verbose_name
self.primary_key = primary_key self.primary_key = primary_key
self.maxlength, self.unique = maxlength, unique self.maxlength, self.unique = maxlength, unique
self.blank, self.null = blank, null self.blank, self.null = blank, null
@ -110,16 +111,23 @@ class Field(object):
self.radio_admin = radio_admin self.radio_admin = radio_admin
self.help_text = help_text self.help_text = help_text
self.db_column = db_column 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 self.db_index = db_index
# Increase the creation counter, and save our local copy. # Increase the creation counter, and save our local copy.
self.creation_counter = Field.creation_counter self.creation_counter = Field.creation_counter
Field.creation_counter += 1 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.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): def set_name(self, name):
self.name = name self.name = name

View File

@ -1,21 +1,63 @@
from django.db.models.fields import Field, IntegerField 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.translation import gettext_lazy, string_concat
from django.utils.functional import curry from django.utils.functional import curry
from django.core import formfields from django.core import formfields
# Values for Relation.edit_inline. # Values for Relation.edit_inline.
TABULAR, STACKED = 1, 2 TABULAR, STACKED = 1, 2
RECURSIVE_RELATIONSHIP_CONSTANT = 'self' 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 #HACK
class SharedMethods(object): class SharedMethods(RelatedField):
def get_attname(self): def get_attname(self):
return '%s_id' % self.name return '%s_id' % self.name
def get_validator_unique_lookup_type(self): def get_validator_unique_lookup_type(self):
return '%s__%s__exact' % (self.name, self.rel.get_related_field().name) return '%s__%s__exact' % (self.name, self.rel.get_related_field().name)
class ForeignKey(SharedMethods,Field): class ForeignKey(SharedMethods,Field):
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, to, to_field=None, **kwargs): def __init__(self, to, to_field=None, **kwargs):
@ -98,7 +140,6 @@ class ForeignKey(SharedMethods,Field):
def contribute_to_related_class(self, cls, related): def contribute_to_related_class(self, cls, related):
rel_obj_name = related.get_method_name_part() rel_obj_name = related.get_method_name_part()
# Add "get_thingie" methods for many-to-one related objects. # Add "get_thingie" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice() # 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)) 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)) rel_class=related.model, rel_field=related.field))
class ManyToManyField(Field): class ManyToManyField(RelatedField,Field):
def __init__(self, to, **kwargs): def __init__(self, to, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural) kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
kwargs['rel'] = ManyToMany(to, kwargs.pop('singular', None), kwargs['rel'] = ManyToMany(to, kwargs.pop('singular', None),
@ -225,6 +266,9 @@ class ManyToManyField(Field):
func.alters_data = True func.alters_data = True
setattr(cls, 'set_%s' % related.opts.module_name, func) setattr(cls, 'set_%s' % related.opts.module_name, func)
def set_attributes_from_rel(self):
pass
class ManyToOne: class ManyToOne:
def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, 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, 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.query import orderlist2sql
from django.db.models.exceptions import FieldDoesNotExist from django.db.models.exceptions import FieldDoesNotExist
from bisect import bisect
class Options: class Options:
def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='', 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, where_constraints=None, object_name=None, app_label=None,
exceptions=None, permissions=None, get_latest_by=None, exceptions=None, permissions=None, get_latest_by=None,
order_with_respect_to=None, module_constants=None): order_with_respect_to=None, module_constants=None):
# Move many-to-many related fields from self.fields into self.many_to_many. # Move many-to-many related fields from self.fields into self.many_to_many.
self.fields, 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.module_name, self.verbose_name = module_name, verbose_name
self.verbose_name_plural = verbose_name_plural or verbose_name + 's' self.verbose_name_plural = verbose_name_plural or verbose_name + 's'
self.db_table = db_table self.db_table = db_table
@ -28,14 +26,22 @@ class Options:
self.permissions = permissions or [] self.permissions = permissions or []
self.object_name, self.app_label = object_name, app_label self.object_name, self.app_label = object_name, app_label
self.get_latest_by = get_latest_by 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.order_with_respect_to = self.get_field(order_with_respect_to)
self.ordering = ('_order',) self.ordering = ('_order',)
else: else:
self.order_with_respect_to = None self.order_with_respect_to = None
self.module_constants = module_constants or {}
self.admin = admin
# Calculate one_to_one_field. # Calculate one_to_one_field.
self.one_to_one_field = None self.one_to_one_field = None
for f in self.fields: for f in self.fields:
@ -51,7 +57,9 @@ class Options:
# If a primary_key field hasn't been specified, add an # If a primary_key field hasn't been specified, add an
# auto-incrementing primary-key ID field automatically. # auto-incrementing primary-key ID field automatically.
if self.pk is None: 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] self.pk = self.fields[0]
# Cache whether this has an AutoField. # Cache whether this has an AutoField.
self.has_auto_field = False self.has_auto_field = False
@ -64,6 +72,15 @@ class Options:
#HACK #HACK
self.limit_choices_to = {} 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): def __repr__(self):
return '<Options for %s>' % self.module_name return '<Options for %s>' % self.module_name