Split up of models/__init__.py complete, tests run and admin works to a degree.

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@1696 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robert Wittams 2005-12-16 13:49:52 +00:00
parent 350e8a3523
commit 6f0861b256
9 changed files with 934 additions and 919 deletions

View File

@ -4,7 +4,8 @@ from django.utils.text import capfirst
from django.utils.functional import curry
from django.contrib.admin.views.main import AdminBoundField
from django.db.models.fields import BoundField, Field
from django.db.models import BoundRelatedObject, TABULAR, STACKED
from django.db.models.related import BoundRelatedObject
from django.db.models.fields import TABULAR, STACKED
from django.db import models
from django.conf.settings import ADMIN_MEDIA_PREFIX
import re

View File

@ -24,6 +24,8 @@ from django.utils.html import escape
import operator
from itertools import izip
from django.db.models.query import handle_legacy_orderlist
# The system will display a "Show all" link only if the total result count
# is less than or equal to this setting.
MAX_SHOW_ALL_ALLOWED = 200
@ -231,7 +233,7 @@ class ChangeList(object):
ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
# Normalize it to new-style ordering.
ordering = models.handle_legacy_orderlist(ordering)
ordering = handle_legacy_orderlist(ordering)
if ordering[0].startswith('-'):
order_field, order_type = ordering[0][1:], 'desc'

View File

@ -1,6 +1,5 @@
from copy import copy
from math import ceil
from django.db.models import ModelBase
class InvalidPage(Exception):
pass

View File

@ -1,36 +1,24 @@
from django.conf import settings
from django.core import formfields, validators
from django.core.exceptions import ObjectDoesNotExist
from django.db import backend, connection
from django.db.models.fields import *
from django.utils.functional import curry
from django.utils.text import capfirst
import copy, datetime, os, re, sys, types
from django.db.models.loading import get_installed_models, get_installed_model_modules
from django.db.models.manipulators import ManipulatorDescriptor, ModelAddManipulator, ModelChangeManipulator
from django.db.models.query import Q, orderlist2sql
from django.db.models.query import Q
from django.db.models.manager import Manager
from django.db.models.base import Model
from django.db.models.fields import *
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.exceptions import FieldDoesNotExist, BadKeywordArguments
# Admin stages.
ADD, CHANGE, BOTH = 1, 2, 3
# Prefix (in Python path style) to location of models.
MODEL_PREFIX = 'django.models'
# Methods on models with the following prefix will be removed and
# converted to module-level functions.
MODEL_FUNCTIONS_PREFIX = '_module_'
# Methods on models with the following prefix will be removed and
# converted to manipulator methods.
MANIPULATOR_FUNCTIONS_PREFIX = '_manipulator_'
#def get_module(app_label, module_name):
# return __import__('%s.%s.%s' % (MODEL_PREFIX, app_label, module_name), '', '', [''])
@ -38,7 +26,6 @@ MANIPULATOR_FUNCTIONS_PREFIX = '_manipulator_'
# 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
@ -64,905 +51,10 @@ class LazyDate:
# MAIN CLASSES #
################
class FieldDoesNotExist(Exception):
pass
class BadKeywordArguments(Exception):
pass
class BoundRelatedObject(object):
def __init__(self, related_object, field_mapping, original):
self.relation = related_object
self.field_mappings = field_mapping[related_object.opts.module_name]
def template_name(self):
raise NotImplementedError
def __repr__(self):
return repr(self.__dict__)
class RelatedObject(object):
def __init__(self, parent_opts, model, field):
self.parent_opts = parent_opts
self.model = model
self.opts = model._meta
self.field = field
self.edit_inline = field.rel.edit_inline
self.name = self.opts.module_name
self.var_name = self.opts.object_name.lower()
def flatten_data(self, follow, obj=None):
new_data = {}
rel_instances = self.get_list(obj)
for i, rel_instance in enumerate(rel_instances):
instance_data = {}
for f in self.opts.fields + self.opts.many_to_many:
# TODO: Fix for recursive manipulators.
fol = follow.get(f.name, None)
if fol:
field_data = f.flatten_data(fol, rel_instance)
for name, value in field_data.items():
instance_data['%s.%d.%s' % (self.var_name, i, name)] = value
new_data.update(instance_data)
return new_data
def extract_data(self, data):
"""
Pull out the data meant for inline objects of this class,
i.e. anything starting with our module name.
"""
return data # TODO
def get_list(self, parent_instance=None):
"Get the list of this type of object from an instance of the parent class."
if parent_instance != None:
func_name = 'get_%s_list' % self.get_method_name_part()
func = getattr(parent_instance, func_name)
list = func()
count = len(list) + self.field.rel.num_extra_on_change
if self.field.rel.min_num_in_admin:
count = max(count, self.field.rel.min_num_in_admin)
if self.field.rel.max_num_in_admin:
count = min(count, self.field.rel.max_num_in_admin)
change = count - len(list)
if change > 0:
return list + [None for _ in range(change)]
if change < 0:
return list[:change]
else: # Just right
return list
else:
return [None for _ in range(self.field.rel.num_in_admin)]
def editable_fields(self):
"Get the fields in this class that should be edited inline."
return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field]
def get_follow(self, override=None):
if isinstance(override, bool):
if override:
over = {}
else:
return None
else:
if override:
over = override.copy()
elif self.edit_inline:
over = {}
else:
return None
over[self.field.name] = False
return self.opts.get_follow(over)
def __repr__(self):
return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
def get_manipulator_fields(self, opts, manipulator, change, follow):
# TODO: Remove core fields stuff.
if manipulator.original_object:
meth_name = 'get_%s_count' % self.get_method_name_part()
count = getattr(manipulator.original_object, meth_name)()
count += self.field.rel.num_extra_on_change
if self.field.rel.min_num_in_admin:
count = max(count, self.field.rel.min_num_in_admin)
if self.field.rel.max_num_in_admin:
count = min(count, self.field.rel.max_num_in_admin)
else:
count = self.field.rel.num_in_admin
fields = []
for i in range(count):
for f in self.opts.fields + self.opts.many_to_many:
if follow.get(f.name, False):
prefix = '%s.%d.' % (self.var_name, i)
fields.extend(f.get_manipulator_fields(self.opts, manipulator, change, name_prefix=prefix, rel=True))
return fields
def bind(self, field_mapping, original, bound_related_object_class=BoundRelatedObject):
return bound_related_object_class(self, field_mapping, original)
def get_method_name_part(self):
# This method encapsulates the logic that decides what name to give a
# method that retrieves related many-to-one or many-to-many objects.
# Usually it just uses the lower-cased object_name, but if the related
# object is in another app, the related object's app_label is appended.
#
# Examples:
#
# # Normal case -- a related object in the same app.
# # This method returns "choice".
# Poll.get_choice_list()
#
# # A related object in a different app.
# # This method returns "lcom_bestofaward".
# Place.get_lcom_bestofaward_list() # "lcom_bestofaward"
rel_obj_name = self.field.rel.related_name or self.opts.object_name.lower()
if self.parent_opts.app_label != self.opts.app_label:
rel_obj_name = '%s_%s' % (self.opts.app_label, rel_obj_name)
return rel_obj_name
class Options:
def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='',
fields=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
self.ordering = ordering or []
self.unique_together = unique_together or []
self.where_constraints = where_constraints or []
self.exceptions = exceptions or []
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 = 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:
if isinstance(f.rel, OneToOne):
self.one_to_one_field = f
break
# Cache the primary-key field.
self.pk = None
for f in self.fields:
if f.primary_key:
self.pk = f
break
# 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))
self.pk = self.fields[0]
# Cache whether this has an AutoField.
self.has_auto_field = False
for f in self.fields:
is_auto = isinstance(f, AutoField)
if is_auto and self.has_auto_field:
raise AssertionError, "A model can't have more than one AutoField."
elif is_auto:
self.has_auto_field = True
#HACK
self.limit_choices_to = {}
def __repr__(self):
return '<Options for %s>' % self.module_name
# def get_model_module(self):
# return get_module(self.app_label, self.module_name)
def get_content_type_id(self):
"Returns the content-type ID for this object type."
if not hasattr(self, '_content_type_id'):
import django.models.core
manager = django.models.core.ContentType.objects
self._content_type_id = \
manager.get_object(python_module_name__exact=self.module_name,
package__label__exact=self.app_label).id
return self._content_type_id
def get_field(self, name, many_to_many=True):
"""
Returns the requested field by name. Raises FieldDoesNotExist on error.
"""
to_search = many_to_many and (self.fields + self.many_to_many) or self.fields
for f in to_search:
if f.name == name:
return f
raise FieldDoesNotExist, "name=%s" % name
def get_order_sql(self, table_prefix=''):
"Returns the full 'ORDER BY' clause for this object, according to self.ordering."
if not self.ordering: return ''
pre = table_prefix and (table_prefix + '.') or ''
return 'ORDER BY ' + orderlist2sql(self.ordering, self, pre)
def get_add_permission(self):
return 'add_%s' % self.object_name.lower()
def get_change_permission(self):
return 'change_%s' % self.object_name.lower()
def get_delete_permission(self):
return 'delete_%s' % self.object_name.lower()
def get_all_related_objects(self):
try: # Try the cache first.
return self._all_related_objects
except AttributeError:
module_list = get_installed_model_modules()
rel_objs = []
for mod in module_list:
for klass in mod._MODELS:
for f in klass._meta.fields:
if f.rel and self == f.rel.to._meta:
rel_objs.append(RelatedObject(self, klass, f))
self._all_related_objects = rel_objs
return rel_objs
def get_followed_related_objects(self, follow=None):
if follow == None:
follow = self.get_follow()
return [f for f in self.get_all_related_objects() if follow.get(f.name, None)]
def get_data_holders(self, follow=None):
if follow == None:
follow = self.get_follow()
return [f for f in self.fields + self.many_to_many + self.get_all_related_objects() if follow.get(f.name, None)]
def get_follow(self, override=None):
follow = {}
for f in self.fields + self.many_to_many + self.get_all_related_objects():
if override and override.has_key(f.name):
child_override = override[f.name]
else:
child_override = None
fol = f.get_follow(child_override)
if fol:
follow[f.name] = fol
return follow
def get_all_related_many_to_many_objects(self):
module_list = get_installed_model_modules()
rel_objs = []
for mod in module_list:
for klass in mod._MODELS:
for f in klass._meta.many_to_many:
if f.rel and self == f.rel.to._meta:
rel_objs.append(RelatedObject(self, klass, f))
return rel_objs
def get_ordered_objects(self):
"Returns a list of Options objects that are ordered with respect to this object."
if not hasattr(self, '_ordered_objects'):
objects = []
#HACK
#for klass in get_app(self.app_label)._MODELS:
# opts = klass._meta
# if opts.order_with_respect_to and opts.order_with_respect_to.rel \
# and self == opts.order_with_respect_to.rel.to._meta:
# objects.append(opts)
self._ordered_objects = objects
return self._ordered_objects
def has_field_type(self, field_type, follow=None):
"""
Returns True if this object's admin form has at least one of the given
field_type (e.g. FileField).
"""
# TODO: follow
if not hasattr(self, '_field_types'):
self._field_types = {}
if not self._field_types.has_key(field_type):
try:
# First check self.fields.
for f in self.fields:
if isinstance(f, field_type):
raise StopIteration
# Failing that, check related fields.
for related in self.get_followed_related_objects(follow):
for f in related.opts.fields:
if isinstance(f, field_type):
raise StopIteration
except StopIteration:
self._field_types[field_type] = True
else:
self._field_types[field_type] = False
return self._field_types[field_type]
# Calculate the module_name using a poor-man's pluralization.
get_module_name = lambda class_name: class_name.lower() + 's'
# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
get_verbose_name = lambda class_name: re.sub('([A-Z])', ' \\1', class_name).lower().strip()
class ModelBase(type):
"Metaclass for all models"
def __new__(cls, name, bases, attrs):
# If this isn't a subclass of Model, don't do anything special.
if not bases or bases == (object,):
return type.__new__(cls, name, bases, attrs)
try:
meta_attrs = attrs.pop('META').__dict__
del meta_attrs['__module__']
del meta_attrs['__doc__']
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)
opts = Options(
module_name = meta_attrs.pop('module_name', get_module_name(name)),
# If the verbose_name wasn't given, use the class name,
# converted from InitialCaps to "lowercase with spaces".
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),
where_constraints = meta_attrs.pop('where_constraints', None),
object_name = name,
app_label = meta_attrs.pop('app_label', None),
exceptions = meta_attrs.pop('exceptions', None),
permissions = meta_attrs.pop('permissions', None),
get_latest_by = meta_attrs.pop('get_latest_by', None),
order_with_respect_to = meta_attrs.pop('order_with_respect_to', None),
module_constants = meta_attrs.pop('module_constants', None),
)
if meta_attrs != {}:
raise TypeError, "'class META' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
# 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)
# Figure out the app_label by looking one level up.
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
# Create the default manager, if needed.
# TODO: Use weakref because of possible memory leak / circular reference.
if managers:
for m_name, m in managers:
m._prepare(new_class)
setattr(new_class, m_name, m)
new_class._default_manager = managers[0][1]
else:
if hasattr(new_class, 'objects'):
raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % name
m = Manager()
m._prepare(new_class)
new_class.objects = m
new_class._default_manager = m
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)
return new_class
class Model(object):
__metaclass__ = ModelBase
AddManipulator = ManipulatorDescriptor('AddManipulator', ModelAddManipulator)
ChangeManipulator = ManipulatorDescriptor('ChangeManipulator', ModelChangeManipulator)
def __repr__(self):
return '<%s object>' % self.__class__.__name__
def __eq__(self, other):
return isinstance(other, self.__class__) and getattr(self, self._meta.pk.attname) == getattr(other, self._meta.pk.attname)
def __ne__(self, other):
return not self.__eq__(other)
def __init__(self, *args, **kwargs):
if kwargs:
for f in self._meta.fields:
if isinstance(f.rel, ManyToOne):
try:
# Assume object instance was passed in.
rel_obj = kwargs.pop(f.name)
except KeyError:
try:
# Object instance wasn't passed in -- must be an ID.
val = kwargs.pop(f.attname)
except KeyError:
val = f.get_default()
else:
# Object instance was passed in.
# Special case: You can pass in "None" for related objects if it's allowed.
if rel_obj is None and f.null:
val = None
else:
try:
val = getattr(rel_obj, f.rel.get_related_field().attname)
except AttributeError:
raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj))
setattr(self, f.attname, val)
else:
val = kwargs.pop(f.attname, f.get_default())
setattr(self, f.attname, val)
if kwargs:
raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
for i, arg in enumerate(args):
setattr(self, self._meta.fields[i].attname, arg)
def _prepare(cls):
# Creates some methods once self._meta has been populated.
for f in cls._meta.fields:
if f.choices:
setattr(cls, 'get_%s_display' % f.name, curry(cls.__get_FIELD_display, field=f))
if isinstance(f, DateField):
if not f.null:
setattr(cls, 'get_next_by_%s' % f.name, curry(cls.__get_next_or_previous_by_FIELD, field=f, is_next=True))
setattr(cls, 'get_previous_by_%s' % f.name, curry(cls.__get_next_or_previous_by_FIELD, field=f, is_next=False))
elif isinstance(f, FileField):
setattr(cls, 'get_%s_filename' % f.name, curry(cls.__get_FIELD_filename, field=f))
setattr(cls, 'get_%s_url' % f.name, curry(cls.__get_FIELD_url, field=f))
setattr(cls, 'get_%s_size' % f.name, curry(cls.__get_FIELD_size, field=f))
setattr(cls, 'save_%s_file' % f.name, curry(cls.__save_FIELD_file, field=f))
if isinstance(f, ImageField):
# Add get_BLAH_width and get_BLAH_height methods, but only
# if the image field doesn't have width and height cache
# fields.
if not f.width_field:
setattr(cls, 'get_%s_width' % f.name, curry(cls.__get_FIELD_width, field=f))
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()
if isinstance(f.rel, ManyToOne):
setattr(cls, 'get_%s' % f.name, curry(cls.__get_foreign_key_object, field_with_rel=f))
# Create the default class methods.
for f in cls._meta.many_to_many:
# Add "get_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.get_site_list(), Story.get_byline_list()
setattr(cls, 'get_%s_list' % f.rel.singular, curry(cls.__get_many_to_many_objects, field_with_rel=f))
# Add "set_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.set_sites(), Story.set_bylines()
setattr(cls, 'set_%s' % f.name, curry(cls.__set_many_to_many_objects, field_with_rel=f))
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)
_prepare = classmethod(_prepare)
def save(self):
# Run any pre-save hooks.
if hasattr(self, '_pre_save'):
self._pre_save()
non_pks = [f for f in self._meta.fields if not f.primary_key]
cursor = connection.cursor()
# First, try an UPDATE. If that doesn't update anything, do an INSERT.
pk_val = getattr(self, self._meta.pk.attname)
pk_set = bool(pk_val)
record_exists = True
if pk_set:
# Determine whether a record with the primary key already exists.
cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \
(backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), [pk_val])
# If it does already exist, do an UPDATE.
if cursor.fetchone():
db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), False)) for f in non_pks]
cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
(backend.quote_name(self._meta.db_table),
','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]),
backend.quote_name(self._meta.pk.attname)),
db_values + [pk_val])
else:
record_exists = False
if not pk_set or not record_exists:
field_names = [backend.quote_name(f.column) for f in self._meta.fields if not isinstance(f, AutoField)]
db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), True)) for f in self._meta.fields if not isinstance(f, AutoField)]
# If the PK has been manually set, respect that.
if pk_set:
field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)]
db_values += [f.get_db_prep_save(f.pre_save(getattr(self, f.column), True)) for f in self._meta.fields if isinstance(f, AutoField)]
placeholders = ['%s'] * len(field_names)
if self._meta.order_with_respect_to:
field_names.append(backend.quote_name('_order'))
# TODO: This assumes the database supports subqueries.
placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \
(backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.order_with_respect_to.column)))
db_values.append(getattr(self, self._meta.order_with_respect_to.attname))
cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
(backend.quote_name(self._meta.db_table), ','.join(field_names),
','.join(placeholders)), db_values)
if self._meta.has_auto_field and not pk_set:
setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
connection.commit()
# Run any post-save hooks.
if hasattr(self, '_post_save'):
self._post_save()
save.alters_data = True
def delete(self):
assert getattr(self, self._meta.pk.attname) is not None, "%r can't be deleted because it doesn't have an ID."
# Run any pre-delete hooks.
if hasattr(self, '_pre_delete'):
self._pre_delete()
cursor = connection.cursor()
for related in self._meta.get_all_related_objects():
rel_opts_name = related.get_method_name_part()
if isinstance(related.field.rel, OneToOne):
try:
sub_obj = getattr(self, 'get_%s' % rel_opts_name)()
except ObjectDoesNotExist:
pass
else:
sub_obj.delete()
else:
for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)():
sub_obj.delete()
for related in self._meta.get_all_related_many_to_many_objects():
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
(backend.quote_name(related.field.get_m2m_db_table(related.opts)),
backend.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, self._meta.pk.attname)])
for f in self._meta.many_to_many:
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
(backend.quote_name(f.get_m2m_db_table(self._meta)),
backend.quote_name(self._meta.object_name.lower() + '_id')),
[getattr(self, self._meta.pk.attname)])
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
(backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)),
[getattr(self, self._meta.pk.attname)])
connection.commit()
setattr(self, self._meta.pk.attname, None)
for f in self._meta.fields:
if isinstance(f, FileField) and getattr(self, f.attname):
file_name = getattr(self, 'get_%s_filename' % f.name)()
# If the file exists and no other object of this type references it,
# delete it from the filesystem.
if os.path.exists(file_name) and not self._default_manager.get_list(**{'%s__exact' % f.name: getattr(self, f.name)}):
os.remove(file_name)
# Run any post-delete hooks.
if hasattr(self, '_post_delete'):
self._post_delete()
delete.alters_data = True
def __get_FIELD_display(self, field):
value = getattr(self, field.attname)
return dict(field.choices).get(value, value)
def __get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
op = is_next and '>' or '<'
kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
(backend.quote_name(field.column), op, backend.quote_name(field.column),
backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column), op))
param = str(getattr(self, field.attname))
kwargs.setdefault('params', []).extend([param, param, getattr(self, self._meta.pk.attname)])
kwargs['order_by'] = [(not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name]
kwargs['limit'] = 1
return self.__class__._default_manager.get_object(**kwargs)
def __get_next_or_previous_in_order(self, is_next):
cachename = "__%s_order_cache" % is_next
if not hasattr(self, cachename):
op = is_next and '>' or '<'
order_field = self.order_with_respect_to
obj = self._default_manager.get_object(order_by=('_order',),
where=['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \
(backend.quote_name('_order'), op, backend.quote_name('_order'),
backend.quote_name(opts.db_table), backend.quote_name(opts.pk.column)),
'%s=%%s' % backend.quote_name(order_field.column)],
limit=1,
params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)])
setattr(self, cachename, obj)
return getattr(self, cachename)
def __get_FIELD_filename(self, field):
return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
def __get_FIELD_url(self, field):
if getattr(self, field.attname): # value is not blank
import urlparse
return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/')
return ''
def __get_FIELD_size(self, field):
return os.path.getsize(self.__get_FIELD_filename(field))
def __save_FIELD_file(self, field, filename, raw_contents):
directory = field.get_directory_name()
try: # Create the date-based directory if it doesn't exist.
os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
except OSError: # Directory probably already exists.
pass
filename = field.get_filename(filename)
# If the filename already exists, keep adding an underscore to the name of
# the file until the filename doesn't exist.
while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
try:
dot_index = filename.rindex('.')
except ValueError: # filename has no dot
filename += '_'
else:
filename = filename[:dot_index] + '_' + filename[dot_index:]
# Write the file to disk.
setattr(self, field.attname, filename)
full_filename = self.__get_FIELD_filename(field)
fp = open(full_filename, 'wb')
fp.write(raw_contents)
fp.close()
# Save the width and/or height, if applicable.
if isinstance(field, ImageField) and (field.width_field or field.height_field):
from django.utils.images import get_image_dimensions
width, height = get_image_dimensions(full_filename)
if field.width_field:
setattr(self, field.width_field, width)
if field.height_field:
setattr(self, field.height_field, height)
# Save the object, because it has changed.
self.save()
__save_FIELD_file.alters_data = True
def __get_FIELD_width(self, field):
return self.__get_image_dimensions(field)[0]
def __get_FIELD_height(self, field):
return self.__get_image_dimensions(field)[1]
def __get_image_dimensions(self, field):
cachename = "__%s_dimensions_cache" % field.name
if not hasattr(self, cachename):
from django.utils.images import get_image_dimensions
filename = self.__get_FIELD_filename(field)()
setattr(self, cachename, get_image_dimensions(filename))
return getattr(self, cachename)
def __get_foreign_key_object(self, field_with_rel):
cache_var = field_with_rel.get_cache_name()
if not hasattr(self, cache_var):
val = getattr(self, field_with_rel.attname)
if val is None:
raise field_with_rel.rel.to.DoesNotExist
other_field = field_with_rel.rel.get_related_field()
if other_field.rel:
params = {'%s__%s__exact' % (field_with_rel.rel.field_name, other_field.rel.field_name): val}
else:
params = {'%s__exact' % field_with_rel.rel.field_name: val}
retrieved_obj = field_with_rel.rel.to._default_manager.get_object(**params)
setattr(self, cache_var, retrieved_obj)
return getattr(self, cache_var)
def __get_many_to_many_objects(self, field_with_rel):
cache_var = '_%s_cache' % field_with_rel.name
if not hasattr(self, cache_var):
rel_opts = field_with_rel.rel.to._meta
sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s AND b.%s = %%s %s" % \
(','.join(['a.%s' % backend.quote_name(f.column) for f in rel_opts.fields]),
backend.quote_name(rel_opts.db_table),
backend.quote_name(field_with_rel.get_m2m_db_table(self._meta)),
backend.quote_name(rel_opts.pk.column),
backend.quote_name(rel_opts.object_name.lower() + '_id'),
backend.quote_name(self._meta.object_name.lower() + '_id'), rel_opts.get_order_sql('a'))
cursor = connection.cursor()
cursor.execute(sql, [getattr(self, self._meta.pk.attname)])
setattr(self, cache_var, [field_with_rel.rel.to(*row) for row in cursor.fetchall()])
return getattr(self, cache_var)
def __set_many_to_many_objects(self, id_list, field_with_rel):
current_ids = [obj.id for obj in self.__get_many_to_many_objects(field_with_rel)]
ids_to_add, ids_to_delete = dict([(i, 1) for i in id_list]), []
for current_id in current_ids:
if current_id in id_list:
del ids_to_add[current_id]
else:
ids_to_delete.append(current_id)
ids_to_add = ids_to_add.keys()
# Now ids_to_add is a list of IDs to add, and ids_to_delete is a list of IDs to delete.
if not ids_to_delete and not ids_to_add:
return False # No change
rel = field_with_rel.rel.to._meta
m2m_table = field_with_rel.get_m2m_db_table(self._meta)
cursor = connection.cursor()
this_id = getattr(self, self._meta.pk.attname)
if ids_to_delete:
sql = "DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
(backend.quote_name(m2m_table),
backend.quote_name(self._meta.object_name.lower() + '_id'),
backend.quote_name(rel.object_name.lower() + '_id'), ','.join(map(str, ids_to_delete)))
cursor.execute(sql, [this_id])
if ids_to_add:
sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
(backend.quote_name(m2m_table),
backend.quote_name(self._meta.object_name.lower() + '_id'),
backend.quote_name(rel.object_name.lower() + '_id'))
cursor.executemany(sql, [(this_id, i) for i in ids_to_add])
connection.commit()
try:
delattr(self, '_%s_cache' % field_with_rel.name) # clear cache, if it exists
except AttributeError:
pass
return True
__set_many_to_many_objects.alters_data = True
def _get_related(self, method_name, rel_class, rel_field, **kwargs):
kwargs['%s__%s__exact' % (rel_field.name, rel_field.rel.to._meta.pk.name)] = getattr(self, rel_field.rel.get_related_field().attname)
kwargs.update(rel_field.rel.lookup_overrides)
return getattr(rel_class._default_manager, method_name)(**kwargs)
def _add_related(self, rel_class, rel_field, *args, **kwargs):
init_kwargs = dict(zip([f.attname for f in rel_class._meta.fields if f != rel_field and not isinstance(f, AutoField)], args))
init_kwargs.update(kwargs)
for f in rel_class._meta.fields:
if isinstance(f, AutoField):
init_kwargs[f.attname] = None
init_kwargs[rel_field.name] = self
obj = rel_class(**init_kwargs)
obj.save()
return obj
_add_related.alters_data = True
# Handles related many-to-many object retrieval.
# Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count()
def _get_related_many_to_many(self, method_name, rel_class, rel_field, **kwargs):
kwargs['%s__%s__exact' % (rel_field.name, self._meta.pk.name)] = getattr(self, self._meta.pk.attname)
return getattr(rel_class._default_manager, method_name)(**kwargs)
# Handles setting many-to-many related objects.
# Example: Album.set_songs()
def _set_related_many_to_many(self, rel_class, rel_field, id_list):
id_list = map(int, id_list) # normalize to integers
rel = rel_field.rel.to
m2m_table = rel_field.get_m2m_db_table(rel_opts)
this_id = getattr(self, self._meta.pk.attname)
cursor = connection.cursor()
cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
(backend.quote_name(m2m_table),
backend.quote_name(rel.object_name.lower() + '_id')), [this_id])
sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
(backend.quote_name(m2m_table),
backend.quote_name(rel.object_name.lower() + '_id'),
backend.quote_name(rel_opts.object_name.lower() + '_id'))
cursor.executemany(sql, [(this_id, i) for i in id_list])
connection.commit()
############################################
# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
############################################
# ORDERING METHODS #########################
def method_set_order(ordered_obj, self, id_list):
cursor = connection.cursor()
# Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \
(backend.quote_name(ordered_obj.db_table), backend.quote_name('_order'),
backend.quote_name(ordered_obj.order_with_respect_to.column),
backend.quote_name(ordered_obj.pk.column))
rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
connection.commit()
def method_get_order(ordered_obj, self):
cursor = connection.cursor()
# Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY %s" % \
(backend.quote_name(ordered_obj.pk.column),
backend.quote_name(ordered_obj.db_table),
backend.quote_name(ordered_obj.order_with_respect_to.column),
backend.quote_name('_order'))
rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
cursor.execute(sql, [rel_val])
return [r[0] for r in cursor.fetchall()]
##############################################
# HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
##############################################
def get_absolute_url(opts, func, self):
return settings.ABSOLUTE_URL_OVERRIDES.get('%s.%s' % (opts.app_label, opts.module_name), func)(self)

589
django/db/models/base.py Normal file
View File

@ -0,0 +1,589 @@
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 OneToOne, ManyToOne, ManyToMany, RECURSIVE_RELATIONSHIP_CONSTANT
from django.db.models.related import RelatedObject
from django.db.models.manager import Manager
from django.db.models.query import orderlist2sql
from django.db.models.options import Options
from django.db import connection, backend
from django.core.exceptions import ObjectDoesNotExist
from django.utils.functional import curry
import re
import types
import sys
# Calculate the module_name using a poor-man's pluralization.
get_module_name = lambda class_name: class_name.lower() + 's'
# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
get_verbose_name = lambda class_name: re.sub('([A-Z])', ' \\1', class_name).lower().strip()
class ModelBase(type):
"Metaclass for all models"
def __new__(cls, name, bases, attrs):
# If this isn't a subclass of Model, don't do anything special.
if not bases or bases == (object,):
return type.__new__(cls, name, bases, attrs)
try:
meta_attrs = attrs.pop('META').__dict__
del meta_attrs['__module__']
del meta_attrs['__doc__']
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)
opts = Options(
module_name = meta_attrs.pop('module_name', get_module_name(name)),
# If the verbose_name wasn't given, use the class name,
# converted from InitialCaps to "lowercase with spaces".
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),
where_constraints = meta_attrs.pop('where_constraints', None),
object_name = name,
app_label = meta_attrs.pop('app_label', None),
exceptions = meta_attrs.pop('exceptions', None),
permissions = meta_attrs.pop('permissions', None),
get_latest_by = meta_attrs.pop('get_latest_by', None),
order_with_respect_to = meta_attrs.pop('order_with_respect_to', None),
module_constants = meta_attrs.pop('module_constants', None),
)
if meta_attrs != {}:
raise TypeError, "'class META' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
# 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)
# Figure out the app_label by looking one level up.
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
# Create the default manager, if needed.
# TODO: Use weakref because of possible memory leak / circular reference.
if managers:
for m_name, m in managers:
m._prepare(new_class)
setattr(new_class, m_name, m)
new_class._default_manager = managers[0][1]
else:
if hasattr(new_class, 'objects'):
raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % name
m = Manager()
m._prepare(new_class)
new_class.objects = m
new_class._default_manager = m
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)
return new_class
class Model(object):
__metaclass__ = ModelBase
AddManipulator = ManipulatorDescriptor('AddManipulator', ModelAddManipulator)
ChangeManipulator = ManipulatorDescriptor('ChangeManipulator', ModelChangeManipulator)
def __repr__(self):
return '<%s object>' % self.__class__.__name__
def __eq__(self, other):
return isinstance(other, self.__class__) and getattr(self, self._meta.pk.attname) == getattr(other, self._meta.pk.attname)
def __ne__(self, other):
return not self.__eq__(other)
def __init__(self, *args, **kwargs):
if kwargs:
for f in self._meta.fields:
if isinstance(f.rel, ManyToOne):
try:
# Assume object instance was passed in.
rel_obj = kwargs.pop(f.name)
except KeyError:
try:
# Object instance wasn't passed in -- must be an ID.
val = kwargs.pop(f.attname)
except KeyError:
val = f.get_default()
else:
# Object instance was passed in.
# Special case: You can pass in "None" for related objects if it's allowed.
if rel_obj is None and f.null:
val = None
else:
try:
val = getattr(rel_obj, f.rel.get_related_field().attname)
except AttributeError:
raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj))
setattr(self, f.attname, val)
else:
val = kwargs.pop(f.attname, f.get_default())
setattr(self, f.attname, val)
if kwargs:
raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
for i, arg in enumerate(args):
setattr(self, self._meta.fields[i].attname, arg)
def _prepare(cls):
# Creates some methods once self._meta has been populated.
for f in cls._meta.fields:
if f.choices:
setattr(cls, 'get_%s_display' % f.name, curry(cls.__get_FIELD_display, field=f))
if isinstance(f, DateField):
if not f.null:
setattr(cls, 'get_next_by_%s' % f.name, curry(cls.__get_next_or_previous_by_FIELD, field=f, is_next=True))
setattr(cls, 'get_previous_by_%s' % f.name, curry(cls.__get_next_or_previous_by_FIELD, field=f, is_next=False))
elif isinstance(f, FileField):
setattr(cls, 'get_%s_filename' % f.name, curry(cls.__get_FIELD_filename, field=f))
setattr(cls, 'get_%s_url' % f.name, curry(cls.__get_FIELD_url, field=f))
setattr(cls, 'get_%s_size' % f.name, curry(cls.__get_FIELD_size, field=f))
setattr(cls, 'save_%s_file' % f.name, curry(cls.__save_FIELD_file, field=f))
if isinstance(f, ImageField):
# Add get_BLAH_width and get_BLAH_height methods, but only
# if the image field doesn't have width and height cache
# fields.
if not f.width_field:
setattr(cls, 'get_%s_width' % f.name, curry(cls.__get_FIELD_width, field=f))
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()
if isinstance(f.rel, ManyToOne):
setattr(cls, 'get_%s' % f.name, curry(cls.__get_foreign_key_object, field_with_rel=f))
# Create the default class methods.
for f in cls._meta.many_to_many:
# Add "get_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.get_site_list(), Story.get_byline_list()
setattr(cls, 'get_%s_list' % f.rel.singular, curry(cls.__get_many_to_many_objects, field_with_rel=f))
# Add "set_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.set_sites(), Story.set_bylines()
setattr(cls, 'set_%s' % f.name, curry(cls.__set_many_to_many_objects, field_with_rel=f))
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)
_prepare = classmethod(_prepare)
def save(self):
# Run any pre-save hooks.
if hasattr(self, '_pre_save'):
self._pre_save()
non_pks = [f for f in self._meta.fields if not f.primary_key]
cursor = connection.cursor()
# First, try an UPDATE. If that doesn't update anything, do an INSERT.
pk_val = getattr(self, self._meta.pk.attname)
pk_set = bool(pk_val)
record_exists = True
if pk_set:
# Determine whether a record with the primary key already exists.
cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \
(backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), [pk_val])
# If it does already exist, do an UPDATE.
if cursor.fetchone():
db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), False)) for f in non_pks]
cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
(backend.quote_name(self._meta.db_table),
','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]),
backend.quote_name(self._meta.pk.attname)),
db_values + [pk_val])
else:
record_exists = False
if not pk_set or not record_exists:
field_names = [backend.quote_name(f.column) for f in self._meta.fields if not isinstance(f, AutoField)]
db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), True)) for f in self._meta.fields if not isinstance(f, AutoField)]
# If the PK has been manually set, respect that.
if pk_set:
field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)]
db_values += [f.get_db_prep_save(f.pre_save(getattr(self, f.column), True)) for f in self._meta.fields if isinstance(f, AutoField)]
placeholders = ['%s'] * len(field_names)
if self._meta.order_with_respect_to:
field_names.append(backend.quote_name('_order'))
# TODO: This assumes the database supports subqueries.
placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \
(backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.order_with_respect_to.column)))
db_values.append(getattr(self, self._meta.order_with_respect_to.attname))
cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
(backend.quote_name(self._meta.db_table), ','.join(field_names),
','.join(placeholders)), db_values)
if self._meta.has_auto_field and not pk_set:
setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
connection.commit()
# Run any post-save hooks.
if hasattr(self, '_post_save'):
self._post_save()
save.alters_data = True
def delete(self):
assert getattr(self, self._meta.pk.attname) is not None, "%r can't be deleted because it doesn't have an ID."
# Run any pre-delete hooks.
if hasattr(self, '_pre_delete'):
self._pre_delete()
cursor = connection.cursor()
for related in self._meta.get_all_related_objects():
rel_opts_name = related.get_method_name_part()
if isinstance(related.field.rel, OneToOne):
try:
sub_obj = getattr(self, 'get_%s' % rel_opts_name)()
except ObjectDoesNotExist:
pass
else:
sub_obj.delete()
else:
for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)():
sub_obj.delete()
for related in self._meta.get_all_related_many_to_many_objects():
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
(backend.quote_name(related.field.get_m2m_db_table(related.opts)),
backend.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, self._meta.pk.attname)])
for f in self._meta.many_to_many:
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
(backend.quote_name(f.get_m2m_db_table(self._meta)),
backend.quote_name(self._meta.object_name.lower() + '_id')),
[getattr(self, self._meta.pk.attname)])
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
(backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)),
[getattr(self, self._meta.pk.attname)])
connection.commit()
setattr(self, self._meta.pk.attname, None)
for f in self._meta.fields:
if isinstance(f, FileField) and getattr(self, f.attname):
file_name = getattr(self, 'get_%s_filename' % f.name)()
# If the file exists and no other object of this type references it,
# delete it from the filesystem.
if os.path.exists(file_name) and not self._default_manager.get_list(**{'%s__exact' % f.name: getattr(self, f.name)}):
os.remove(file_name)
# Run any post-delete hooks.
if hasattr(self, '_post_delete'):
self._post_delete()
delete.alters_data = True
def __get_FIELD_display(self, field):
value = getattr(self, field.attname)
return dict(field.choices).get(value, value)
def __get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
op = is_next and '>' or '<'
kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
(backend.quote_name(field.column), op, backend.quote_name(field.column),
backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column), op))
param = str(getattr(self, field.attname))
kwargs.setdefault('params', []).extend([param, param, getattr(self, self._meta.pk.attname)])
kwargs['order_by'] = [(not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name]
kwargs['limit'] = 1
return self.__class__._default_manager.get_object(**kwargs)
def __get_next_or_previous_in_order(self, is_next):
cachename = "__%s_order_cache" % is_next
if not hasattr(self, cachename):
op = is_next and '>' or '<'
order_field = self.order_with_respect_to
obj = self._default_manager.get_object(order_by=('_order',),
where=['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \
(backend.quote_name('_order'), op, backend.quote_name('_order'),
backend.quote_name(opts.db_table), backend.quote_name(opts.pk.column)),
'%s=%%s' % backend.quote_name(order_field.column)],
limit=1,
params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)])
setattr(self, cachename, obj)
return getattr(self, cachename)
def __get_FIELD_filename(self, field):
return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
def __get_FIELD_url(self, field):
if getattr(self, field.attname): # value is not blank
import urlparse
return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/')
return ''
def __get_FIELD_size(self, field):
return os.path.getsize(self.__get_FIELD_filename(field))
def __save_FIELD_file(self, field, filename, raw_contents):
directory = field.get_directory_name()
try: # Create the date-based directory if it doesn't exist.
os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
except OSError: # Directory probably already exists.
pass
filename = field.get_filename(filename)
# If the filename already exists, keep adding an underscore to the name of
# the file until the filename doesn't exist.
while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
try:
dot_index = filename.rindex('.')
except ValueError: # filename has no dot
filename += '_'
else:
filename = filename[:dot_index] + '_' + filename[dot_index:]
# Write the file to disk.
setattr(self, field.attname, filename)
full_filename = self.__get_FIELD_filename(field)
fp = open(full_filename, 'wb')
fp.write(raw_contents)
fp.close()
# Save the width and/or height, if applicable.
if isinstance(field, ImageField) and (field.width_field or field.height_field):
from django.utils.images import get_image_dimensions
width, height = get_image_dimensions(full_filename)
if field.width_field:
setattr(self, field.width_field, width)
if field.height_field:
setattr(self, field.height_field, height)
# Save the object, because it has changed.
self.save()
__save_FIELD_file.alters_data = True
def __get_FIELD_width(self, field):
return self.__get_image_dimensions(field)[0]
def __get_FIELD_height(self, field):
return self.__get_image_dimensions(field)[1]
def __get_image_dimensions(self, field):
cachename = "__%s_dimensions_cache" % field.name
if not hasattr(self, cachename):
from django.utils.images import get_image_dimensions
filename = self.__get_FIELD_filename(field)()
setattr(self, cachename, get_image_dimensions(filename))
return getattr(self, cachename)
def __get_foreign_key_object(self, field_with_rel):
cache_var = field_with_rel.get_cache_name()
if not hasattr(self, cache_var):
val = getattr(self, field_with_rel.attname)
if val is None:
raise field_with_rel.rel.to.DoesNotExist
other_field = field_with_rel.rel.get_related_field()
if other_field.rel:
params = {'%s__%s__exact' % (field_with_rel.rel.field_name, other_field.rel.field_name): val}
else:
params = {'%s__exact' % field_with_rel.rel.field_name: val}
retrieved_obj = field_with_rel.rel.to._default_manager.get_object(**params)
setattr(self, cache_var, retrieved_obj)
return getattr(self, cache_var)
def __get_many_to_many_objects(self, field_with_rel):
cache_var = '_%s_cache' % field_with_rel.name
if not hasattr(self, cache_var):
rel_opts = field_with_rel.rel.to._meta
sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s AND b.%s = %%s %s" % \
(','.join(['a.%s' % backend.quote_name(f.column) for f in rel_opts.fields]),
backend.quote_name(rel_opts.db_table),
backend.quote_name(field_with_rel.get_m2m_db_table(self._meta)),
backend.quote_name(rel_opts.pk.column),
backend.quote_name(rel_opts.object_name.lower() + '_id'),
backend.quote_name(self._meta.object_name.lower() + '_id'), rel_opts.get_order_sql('a'))
cursor = connection.cursor()
cursor.execute(sql, [getattr(self, self._meta.pk.attname)])
setattr(self, cache_var, [field_with_rel.rel.to(*row) for row in cursor.fetchall()])
return getattr(self, cache_var)
def __set_many_to_many_objects(self, id_list, field_with_rel):
current_ids = [obj.id for obj in self.__get_many_to_many_objects(field_with_rel)]
ids_to_add, ids_to_delete = dict([(i, 1) for i in id_list]), []
for current_id in current_ids:
if current_id in id_list:
del ids_to_add[current_id]
else:
ids_to_delete.append(current_id)
ids_to_add = ids_to_add.keys()
# Now ids_to_add is a list of IDs to add, and ids_to_delete is a list of IDs to delete.
if not ids_to_delete and not ids_to_add:
return False # No change
rel = field_with_rel.rel.to._meta
m2m_table = field_with_rel.get_m2m_db_table(self._meta)
cursor = connection.cursor()
this_id = getattr(self, self._meta.pk.attname)
if ids_to_delete:
sql = "DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
(backend.quote_name(m2m_table),
backend.quote_name(self._meta.object_name.lower() + '_id'),
backend.quote_name(rel.object_name.lower() + '_id'), ','.join(map(str, ids_to_delete)))
cursor.execute(sql, [this_id])
if ids_to_add:
sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
(backend.quote_name(m2m_table),
backend.quote_name(self._meta.object_name.lower() + '_id'),
backend.quote_name(rel.object_name.lower() + '_id'))
cursor.executemany(sql, [(this_id, i) for i in ids_to_add])
connection.commit()
try:
delattr(self, '_%s_cache' % field_with_rel.name) # clear cache, if it exists
except AttributeError:
pass
return True
__set_many_to_many_objects.alters_data = True
def _get_related(self, method_name, rel_class, rel_field, **kwargs):
kwargs['%s__%s__exact' % (rel_field.name, rel_field.rel.to._meta.pk.name)] = getattr(self, rel_field.rel.get_related_field().attname)
kwargs.update(rel_field.rel.lookup_overrides)
return getattr(rel_class._default_manager, method_name)(**kwargs)
def _add_related(self, rel_class, rel_field, *args, **kwargs):
init_kwargs = dict(zip([f.attname for f in rel_class._meta.fields if f != rel_field and not isinstance(f, AutoField)], args))
init_kwargs.update(kwargs)
for f in rel_class._meta.fields:
if isinstance(f, AutoField):
init_kwargs[f.attname] = None
init_kwargs[rel_field.name] = self
obj = rel_class(**init_kwargs)
obj.save()
return obj
_add_related.alters_data = True
# Handles related many-to-many object retrieval.
# Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count()
def _get_related_many_to_many(self, method_name, rel_class, rel_field, **kwargs):
kwargs['%s__%s__exact' % (rel_field.name, self._meta.pk.name)] = getattr(self, self._meta.pk.attname)
return getattr(rel_class._default_manager, method_name)(**kwargs)
# Handles setting many-to-many related objects.
# Example: Album.set_songs()
def _set_related_many_to_many(self, rel_class, rel_field, id_list):
id_list = map(int, id_list) # normalize to integers
rel = rel_field.rel.to
m2m_table = rel_field.get_m2m_db_table(rel_opts)
this_id = getattr(self, self._meta.pk.attname)
cursor = connection.cursor()
cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
(backend.quote_name(m2m_table),
backend.quote_name(rel.object_name.lower() + '_id')), [this_id])
sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
(backend.quote_name(m2m_table),
backend.quote_name(rel.object_name.lower() + '_id'),
backend.quote_name(rel_opts.object_name.lower() + '_id'))
cursor.executemany(sql, [(this_id, i) for i in id_list])
connection.commit()
############################################
# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
############################################
# ORDERING METHODS #########################
def method_set_order(ordered_obj, self, id_list):
cursor = connection.cursor()
# Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \
(backend.quote_name(ordered_obj.db_table), backend.quote_name('_order'),
backend.quote_name(ordered_obj.order_with_respect_to.column),
backend.quote_name(ordered_obj.pk.column))
rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
connection.commit()
def method_get_order(ordered_obj, self):
cursor = connection.cursor()
# Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY %s" % \
(backend.quote_name(ordered_obj.pk.column),
backend.quote_name(ordered_obj.db_table),
backend.quote_name(ordered_obj.order_with_respect_to.column),
backend.quote_name('_order'))
rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
cursor.execute(sql, [rel_val])
return [r[0] for r in cursor.fetchall()]
##############################################
# HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
##############################################
def get_absolute_url(opts, func, self):
return settings.ABSOLUTE_URL_OVERRIDES.get('%s.%s' % (opts.app_label, opts.module_name), func)(self)

View File

@ -0,0 +1,5 @@
class FieldDoesNotExist(Exception):
pass
class BadKeywordArguments(Exception):
pass

191
django/db/models/options.py Normal file
View File

@ -0,0 +1,191 @@
from django.db.models.related import RelatedObject
from django.db.models.fields import OneToOne, ManyToMany
from django.db.models.fields import AutoField
from django.db.models.loading import get_installed_model_modules
from django.db.models.query import orderlist2sql
from django.db.models.exceptions import FieldDoesNotExist
class Options:
def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='',
fields=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
self.ordering = ordering or []
self.unique_together = unique_together or []
self.where_constraints = where_constraints or []
self.exceptions = exceptions or []
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 = 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:
if isinstance(f.rel, OneToOne):
self.one_to_one_field = f
break
# Cache the primary-key field.
self.pk = None
for f in self.fields:
if f.primary_key:
self.pk = f
break
# 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))
self.pk = self.fields[0]
# Cache whether this has an AutoField.
self.has_auto_field = False
for f in self.fields:
is_auto = isinstance(f, AutoField)
if is_auto and self.has_auto_field:
raise AssertionError, "A model can't have more than one AutoField."
elif is_auto:
self.has_auto_field = True
#HACK
self.limit_choices_to = {}
def __repr__(self):
return '<Options for %s>' % self.module_name
# def get_model_module(self):
# return get_module(self.app_label, self.module_name)
def get_content_type_id(self):
"Returns the content-type ID for this object type."
if not hasattr(self, '_content_type_id'):
import django.models.core
manager = django.models.core.ContentType.objects
self._content_type_id = \
manager.get_object(python_module_name__exact=self.module_name,
package__label__exact=self.app_label).id
return self._content_type_id
def get_field(self, name, many_to_many=True):
"""
Returns the requested field by name. Raises FieldDoesNotExist on error.
"""
to_search = many_to_many and (self.fields + self.many_to_many) or self.fields
for f in to_search:
if f.name == name:
return f
raise FieldDoesNotExist, "name=%s" % name
def get_order_sql(self, table_prefix=''):
"Returns the full 'ORDER BY' clause for this object, according to self.ordering."
if not self.ordering: return ''
pre = table_prefix and (table_prefix + '.') or ''
return 'ORDER BY ' + orderlist2sql(self.ordering, self, pre)
def get_add_permission(self):
return 'add_%s' % self.object_name.lower()
def get_change_permission(self):
return 'change_%s' % self.object_name.lower()
def get_delete_permission(self):
return 'delete_%s' % self.object_name.lower()
def get_all_related_objects(self):
try: # Try the cache first.
return self._all_related_objects
except AttributeError:
module_list = get_installed_model_modules()
rel_objs = []
for mod in module_list:
for klass in mod._MODELS:
for f in klass._meta.fields:
if f.rel and self == f.rel.to._meta:
rel_objs.append(RelatedObject(self, klass, f))
self._all_related_objects = rel_objs
return rel_objs
def get_followed_related_objects(self, follow=None):
if follow == None:
follow = self.get_follow()
return [f for f in self.get_all_related_objects() if follow.get(f.name, None)]
def get_data_holders(self, follow=None):
if follow == None:
follow = self.get_follow()
return [f for f in self.fields + self.many_to_many + self.get_all_related_objects() if follow.get(f.name, None)]
def get_follow(self, override=None):
follow = {}
for f in self.fields + self.many_to_many + self.get_all_related_objects():
if override and override.has_key(f.name):
child_override = override[f.name]
else:
child_override = None
fol = f.get_follow(child_override)
if fol:
follow[f.name] = fol
return follow
def get_all_related_many_to_many_objects(self):
module_list = get_installed_model_modules()
rel_objs = []
for mod in module_list:
for klass in mod._MODELS:
for f in klass._meta.many_to_many:
if f.rel and self == f.rel.to._meta:
rel_objs.append(RelatedObject(self, klass, f))
return rel_objs
def get_ordered_objects(self):
"Returns a list of Options objects that are ordered with respect to this object."
if not hasattr(self, '_ordered_objects'):
objects = []
#HACK
#for klass in get_app(self.app_label)._MODELS:
# opts = klass._meta
# if opts.order_with_respect_to and opts.order_with_respect_to.rel \
# and self == opts.order_with_respect_to.rel.to._meta:
# objects.append(opts)
self._ordered_objects = objects
return self._ordered_objects
def has_field_type(self, field_type, follow=None):
"""
Returns True if this object's admin form has at least one of the given
field_type (e.g. FileField).
"""
# TODO: follow
if not hasattr(self, '_field_types'):
self._field_types = {}
if not self._field_types.has_key(field_type):
try:
# First check self.fields.
for f in self.fields:
if isinstance(f, field_type):
raise StopIteration
# Failing that, check related fields.
for related in self.get_followed_related_objects(follow):
for f in related.opts.fields:
if isinstance(f, field_type):
raise StopIteration
except StopIteration:
self._field_types[field_type] = True
else:
self._field_types[field_type] = False
return self._field_types[field_type]

View File

@ -1,4 +1,5 @@
from django.db import backend, connection
from django.db.models.exceptions import *
LOOKUP_SEPARATOR = '__'
####################

135
django/db/models/related.py Normal file
View File

@ -0,0 +1,135 @@
class BoundRelatedObject(object):
def __init__(self, related_object, field_mapping, original):
self.relation = related_object
self.field_mappings = field_mapping[related_object.opts.module_name]
def template_name(self):
raise NotImplementedError
def __repr__(self):
return repr(self.__dict__)
class RelatedObject(object):
def __init__(self, parent_opts, model, field):
self.parent_opts = parent_opts
self.model = model
self.opts = model._meta
self.field = field
self.edit_inline = field.rel.edit_inline
self.name = self.opts.module_name
self.var_name = self.opts.object_name.lower()
def flatten_data(self, follow, obj=None):
new_data = {}
rel_instances = self.get_list(obj)
for i, rel_instance in enumerate(rel_instances):
instance_data = {}
for f in self.opts.fields + self.opts.many_to_many:
# TODO: Fix for recursive manipulators.
fol = follow.get(f.name, None)
if fol:
field_data = f.flatten_data(fol, rel_instance)
for name, value in field_data.items():
instance_data['%s.%d.%s' % (self.var_name, i, name)] = value
new_data.update(instance_data)
return new_data
def extract_data(self, data):
"""
Pull out the data meant for inline objects of this class,
i.e. anything starting with our module name.
"""
return data # TODO
def get_list(self, parent_instance=None):
"Get the list of this type of object from an instance of the parent class."
if parent_instance != None:
func_name = 'get_%s_list' % self.get_method_name_part()
func = getattr(parent_instance, func_name)
list = func()
count = len(list) + self.field.rel.num_extra_on_change
if self.field.rel.min_num_in_admin:
count = max(count, self.field.rel.min_num_in_admin)
if self.field.rel.max_num_in_admin:
count = min(count, self.field.rel.max_num_in_admin)
change = count - len(list)
if change > 0:
return list + [None for _ in range(change)]
if change < 0:
return list[:change]
else: # Just right
return list
else:
return [None for _ in range(self.field.rel.num_in_admin)]
def editable_fields(self):
"Get the fields in this class that should be edited inline."
return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field]
def get_follow(self, override=None):
if isinstance(override, bool):
if override:
over = {}
else:
return None
else:
if override:
over = override.copy()
elif self.edit_inline:
over = {}
else:
return None
over[self.field.name] = False
return self.opts.get_follow(over)
def __repr__(self):
return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
def get_manipulator_fields(self, opts, manipulator, change, follow):
# TODO: Remove core fields stuff.
if manipulator.original_object:
meth_name = 'get_%s_count' % self.get_method_name_part()
count = getattr(manipulator.original_object, meth_name)()
count += self.field.rel.num_extra_on_change
if self.field.rel.min_num_in_admin:
count = max(count, self.field.rel.min_num_in_admin)
if self.field.rel.max_num_in_admin:
count = min(count, self.field.rel.max_num_in_admin)
else:
count = self.field.rel.num_in_admin
fields = []
for i in range(count):
for f in self.opts.fields + self.opts.many_to_many:
if follow.get(f.name, False):
prefix = '%s.%d.' % (self.var_name, i)
fields.extend(f.get_manipulator_fields(self.opts, manipulator, change, name_prefix=prefix, rel=True))
return fields
def bind(self, field_mapping, original, bound_related_object_class=BoundRelatedObject):
return bound_related_object_class(self, field_mapping, original)
def get_method_name_part(self):
# This method encapsulates the logic that decides what name to give a
# method that retrieves related many-to-one or many-to-many objects.
# Usually it just uses the lower-cased object_name, but if the related
# object is in another app, the related object's app_label is appended.
#
# Examples:
#
# # Normal case -- a related object in the same app.
# # This method returns "choice".
# Poll.get_choice_list()
#
# # A related object in a different app.
# # This method returns "lcom_bestofaward".
# Place.get_lcom_bestofaward_list() # "lcom_bestofaward"
rel_obj_name = self.field.rel.related_name or self.opts.object_name.lower()
if self.parent_opts.app_label != self.opts.app_label:
rel_obj_name = '%s_%s' % (self.opts.app_label, rel_obj_name)
return rel_obj_name