mirror of
https://github.com/django/django.git
synced 2025-06-29 15:29:13 +00:00
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:
parent
350e8a3523
commit
6f0861b256
@ -4,7 +4,8 @@ from django.utils.text import capfirst
|
|||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
from django.contrib.admin.views.main import AdminBoundField
|
from django.contrib.admin.views.main import AdminBoundField
|
||||||
from django.db.models.fields import BoundField, Field
|
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.db import models
|
||||||
from django.conf.settings import ADMIN_MEDIA_PREFIX
|
from django.conf.settings import ADMIN_MEDIA_PREFIX
|
||||||
import re
|
import re
|
||||||
|
@ -24,6 +24,8 @@ from django.utils.html import escape
|
|||||||
import operator
|
import operator
|
||||||
from itertools import izip
|
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
|
# The system will display a "Show all" link only if the total result count
|
||||||
# is less than or equal to this setting.
|
# is less than or equal to this setting.
|
||||||
MAX_SHOW_ALL_ALLOWED = 200
|
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]
|
ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
|
||||||
|
|
||||||
# Normalize it to new-style ordering.
|
# Normalize it to new-style ordering.
|
||||||
ordering = models.handle_legacy_orderlist(ordering)
|
ordering = handle_legacy_orderlist(ordering)
|
||||||
|
|
||||||
if ordering[0].startswith('-'):
|
if ordering[0].startswith('-'):
|
||||||
order_field, order_type = ordering[0][1:], 'desc'
|
order_field, order_type = ordering[0][1:], 'desc'
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from copy import copy
|
from copy import copy
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from django.db.models import ModelBase
|
|
||||||
|
|
||||||
class InvalidPage(Exception):
|
class InvalidPage(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -1,36 +1,24 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import formfields, validators
|
from django.core import formfields, validators
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.db import backend, connection
|
from django.db import backend, connection
|
||||||
from django.db.models.fields import *
|
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
from django.utils.text import capfirst
|
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.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
|
||||||
from django.db.models.query import Q, orderlist2sql
|
|
||||||
from django.db.models.manager import Manager
|
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.
|
# Admin stages.
|
||||||
ADD, CHANGE, BOTH = 1, 2, 3
|
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):
|
#def get_module(app_label, module_name):
|
||||||
# return __import__('%s.%s.%s' % (MODEL_PREFIX, 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), '', '', [''])
|
# 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
|
||||||
@ -64,905 +51,10 @@ class LazyDate:
|
|||||||
# MAIN CLASSES #
|
# 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
589
django/db/models/base.py
Normal 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)
|
||||||
|
|
||||||
|
|
5
django/db/models/exceptions.py
Normal file
5
django/db/models/exceptions.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class FieldDoesNotExist(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class BadKeywordArguments(Exception):
|
||||||
|
pass
|
191
django/db/models/options.py
Normal file
191
django/db/models/options.py
Normal 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]
|
@ -1,4 +1,5 @@
|
|||||||
from django.db import backend, connection
|
from django.db import backend, connection
|
||||||
|
from django.db.models.exceptions import *
|
||||||
|
|
||||||
LOOKUP_SEPARATOR = '__'
|
LOOKUP_SEPARATOR = '__'
|
||||||
####################
|
####################
|
||||||
|
135
django/db/models/related.py
Normal file
135
django/db/models/related.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user