1
0
mirror of https://github.com/django/django.git synced 2025-07-03 17:29:12 +00:00

Seperate related fields. Add stage works.

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@1700 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robert Wittams 2005-12-16 16:53:27 +00:00
parent c86bfac264
commit 43b41a7565
8 changed files with 323 additions and 291 deletions

View File

@ -5,7 +5,7 @@ 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.related import BoundRelatedObject from django.db.models.related import BoundRelatedObject
from django.db.models.fields import TABULAR, STACKED from django.db.models 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

View File

@ -472,11 +472,13 @@ def log_add_message(user, opts,manipulator,new_object):
pk_value = getattr(new_object, opts.pk.attname) pk_value = getattr(new_object, opts.pk.attname)
LogEntry.objects.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), ADDITION) LogEntry.objects.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), ADDITION)
def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None): def add_stage(request, path, show_delete=False, form_url='', post_url='../change/', post_url_continue='../%s/', object_id_override=None):
mod, opts = _get_mod_opts(app_label, module_name) model, app_label = get_model_and_app(path)
opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_add_permission()): if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
raise PermissionDenied raise PermissionDenied
manipulator = mod.AddManipulator() manipulator = model.AddManipulator()
if request.POST: if request.POST:
new_data = request.POST.copy() new_data = request.POST.copy()
if opts.has_field_type(models.FileField): if opts.has_field_type(models.FileField):
@ -525,7 +527,7 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
if object_id_override is not None: if object_id_override is not None:
c['object_id'] = object_id_override c['object_id'] = object_id_override
return render_change_form(opts, manipulator, app_label, c, add=True) return render_change_form(model, manipulator, app_label, c, add=True)
add_stage = staff_member_required(add_stage) add_stage = staff_member_required(add_stage)
def log_change_message(user, opts,manipulator,new_object): def log_change_message(user, opts,manipulator,new_object):
@ -544,7 +546,7 @@ def log_change_message(user, opts,manipulator,new_object):
LogEntry.objects.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), CHANGE, change_message) LogEntry.objects.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), CHANGE, change_message)
def change_stage(request, path, object_id): def change_stage(request, path, object_id):
print "change_stage", path, object_id
model, app_label = get_model_and_app(path) model, app_label = get_model_and_app(path)
opts = model._meta opts = model._meta
#mod, opts = _get_mod_opts(app_label, module_name) #mod, opts = _get_mod_opts(app_label, module_name)

View File

@ -10,11 +10,14 @@ from django.db.models.loading import get_installed_models, get_installed_model_m
from django.db.models.query import Q from django.db.models.query import Q
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.base import Model
from django.db.models.fields import * from django.db.models.fields import *
from django.db.models.fields.related import *
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db.models.exceptions import FieldDoesNotExist, BadKeywordArguments from django.db.models.exceptions import FieldDoesNotExist, BadKeywordArguments
# Admin stages. # Admin stages.
ADD, CHANGE, BOTH = 1, 2, 3 ADD, CHANGE, BOTH = 1, 2, 3

View File

@ -1,6 +1,6 @@
from django.db.models.manipulators import ManipulatorDescriptor, ModelAddManipulator, ModelChangeManipulator from django.db.models.manipulators import ManipulatorDescriptor, ModelAddManipulator, ModelChangeManipulator
from django.db.models.fields import Field, DateField, FileField, ImageField, AutoField from django.db.models.fields import Field, DateField, FileField, ImageField, AutoField
from django.db.models.fields import OneToOne, ManyToOne, ManyToMany, RECURSIVE_RELATIONSHIP_CONSTANT from django.db.models.fields.related import OneToOne, ManyToOne, ManyToMany, RECURSIVE_RELATIONSHIP_CONSTANT
from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject
from django.db.models.manager import Manager, ManagerDescriptor from django.db.models.manager import Manager, ManagerDescriptor
from django.db.models.query import orderlist2sql from django.db.models.query import orderlist2sql

View File

@ -16,10 +16,6 @@ HORIZONTAL, VERTICAL = 1, 2
BLANK_CHOICE_DASH = [("", "---------")] BLANK_CHOICE_DASH = [("", "---------")]
BLANK_CHOICE_NONE = [("", "None")] BLANK_CHOICE_NONE = [("", "None")]
# Values for Relation.edit_inline.
TABULAR, STACKED = 1, 2
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
# prepares a value for use in a LIKE query # prepares a value for use in a LIKE query
prep_for_like_query = lambda x: str(x).replace("%", "\%").replace("_", "\_") prep_for_like_query = lambda x: str(x).replace("%", "\%").replace("_", "\_")
@ -27,15 +23,7 @@ prep_for_like_query = lambda x: str(x).replace("%", "\%").replace("_", "\_")
# returns the <ul> class for a given radio_admin value # returns the <ul> class for a given radio_admin value
get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
def string_concat(*strings):
""""
lazy variant of string concatenation, needed for translations that are
constructed from multiple parts. Handles lazy strings and non-strings by
first turning all arguments to strings, before joining them.
"""
return ''.join([str(el) for el in strings])
string_concat = lazy(string_concat, str)
def manipulator_valid_rel_key(f, self, field_data, all_data): def manipulator_valid_rel_key(f, self, field_data, all_data):
"Validates that the value is a valid foreign key" "Validates that the value is a valid foreign key"
@ -47,12 +35,11 @@ def manipulator_valid_rel_key(f, self, field_data, all_data):
def manipulator_validator_unique(f, opts, self, field_data, all_data): def manipulator_validator_unique(f, opts, self, field_data, all_data):
"Validates that the value is unique for this field." "Validates that the value is unique for this field."
if f.rel and isinstance(f.rel, ManyToOne):
lookup_type = '%s__%s__exact' % (f.name, f.rel.get_related_field().name) lookup_type = f.get_validator_unique_lookup_type()
else:
lookup_type = '%s__exact' % f.name
try: try:
old_obj = opts.get_model_module().get_object(**{lookup_type: field_data}) old_obj = self.__class__._default_manager.get_object(**{lookup_type: field_data})
except ObjectDoesNotExist: except ObjectDoesNotExist:
return return
if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname): if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname):
@ -103,7 +90,7 @@ class Field(object):
creation_counter = 0 creation_counter = 0
def __init__(self, verbose_name=None, name=None, primary_key=False, def __init__(self, verbose_name=None, name=None, primary_key=False,
maxlength=None, unique=False, blank=False, null=False, db_index=None, maxlength=None, unique=False, blank=False, null=False, db_index=False,
core=False, rel=None, default=NOT_PROVIDED, editable=True, core=False, rel=None, default=NOT_PROVIDED, editable=True,
prepopulate_from=None, unique_for_date=None, unique_for_month=None, prepopulate_from=None, unique_for_date=None, unique_for_month=None,
unique_for_year=None, validator_list=None, choices=None, radio_admin=None, unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
@ -123,22 +110,10 @@ class Field(object):
self.radio_admin = radio_admin self.radio_admin = radio_admin
self.help_text = help_text self.help_text = help_text
self.db_column = db_column self.db_column = db_column
if rel and isinstance(rel, ManyToMany):
if rel.raw_id_admin:
self.help_text = string_concat(self.help_text,
gettext_lazy(' Separate multiple IDs with commas.'))
else:
self.help_text = string_concat(self.help_text,
gettext_lazy(' Hold down "Control", or "Command" on a Mac, to select more than one.'))
# Set db_index to True if the field has a relationship and doesn't explicitly set db_index. # Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
if db_index is None:
if isinstance(rel, OneToOne) or isinstance(rel, ManyToOne): self.db_index = db_index
self.db_index = True
else:
self.db_index = False
else:
self.db_index = db_index
# Increase the creation counter, and save our local copy. # Increase the creation counter, and save our local copy.
self.creation_counter = Field.creation_counter self.creation_counter = Field.creation_counter
@ -151,11 +126,11 @@ class Field(object):
self.verbose_name = self.verbose_name or name.replace('_', ' ') self.verbose_name = self.verbose_name or name.replace('_', ' ')
self.attname, self.column = self.get_attname_column() self.attname, self.column = self.get_attname_column()
def get_attname(self):
return self.name
def get_attname_column(self): def get_attname_column(self):
if isinstance(self.rel, ManyToOne): attname = self.get_attname()
attname = '%s_id' % self.name
else:
attname = self.name
column = self.db_column or attname column = self.db_column or attname
return attname, column return attname, column
@ -213,33 +188,12 @@ class Field(object):
""" """
return [name_prefix + self.name] return [name_prefix + self.name]
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False): def prepare_field_objs_and_params(self, manipulator, name_prefix):
"""
Returns a list of formfields.FormField instances for this field. It
calculates the choices at runtime, not at compile time.
name_prefix is a prefix to prepend to the "field_name" argument.
rel is a boolean specifying whether this field is in a related context.
"""
params = {'validator_list': self.validator_list[:]} params = {'validator_list': self.validator_list[:]}
if self.maxlength and not self.choices: # Don't give SelectFields a maxlength parameter. if self.maxlength and not self.choices: # Don't give SelectFields a maxlength parameter.
params['maxlength'] = self.maxlength params['maxlength'] = self.maxlength
if isinstance(self.rel, ManyToOne):
params['member_name'] = name_prefix + self.attname if self.choices:
if self.rel.raw_id_admin:
field_objs = self.get_manipulator_field_objs()
params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
else:
if self.radio_admin:
field_objs = [formfields.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin)
else:
if self.null:
field_objs = [formfields.NullSelectField]
else:
field_objs = [formfields.SelectField]
params['choices'] = self.get_choices_default()
elif self.choices:
if self.radio_admin: if self.radio_admin:
field_objs = [formfields.RadioSelectField] field_objs = [formfields.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin) params['ul_class'] = get_ul_class(self.radio_admin)
@ -249,6 +203,17 @@ class Field(object):
params['choices'] = self.get_choices_default() params['choices'] = self.get_choices_default()
else: else:
field_objs = self.get_manipulator_field_objs() field_objs = self.get_manipulator_field_objs()
return (field_objs,params)
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
"""
Returns a list of formfields.FormField instances for this field. It
calculates the choices at runtime, not at compile time.
name_prefix is a prefix to prepend to the "field_name" argument.
rel is a boolean specifying whether this field is in a related context.
"""
field_objs, params = self.prepare_field_objs_and_params(manipulator, name_prefix)
# Add the "unique" validator(s). # Add the "unique" validator(s).
for field_name_list in opts.unique_together: for field_name_list in opts.unique_together:
@ -293,6 +258,9 @@ class Field(object):
field_names = self.get_manipulator_field_names(name_prefix) field_names = self.get_manipulator_field_names(name_prefix)
return [man(field_name=field_names[i], **params) for i, man in enumerate(field_objs)] return [man(field_name=field_names[i], **params) for i, man in enumerate(field_objs)]
def get_validator_unique_lookup_type(self):
return '%s__exact' % f.name
def get_manipulator_new_data(self, new_data, rel=False): def get_manipulator_new_data(self, new_data, rel=False):
""" """
Given the full new_data dictionary (from the manipulator), returns this Given the full new_data dictionary (from the manipulator), returns this
@ -684,228 +652,6 @@ class XMLField(TextField):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [curry(formfields.XMLLargeTextField, schema_path=self.schema_path)] return [curry(formfields.XMLLargeTextField, schema_path=self.schema_path)]
class ForeignKey(Field):
empty_strings_allowed = False
def __init__(self, to, to_field=None, **kwargs):
try:
to_name = to._meta.object_name.lower()
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
kwargs['verbose_name'] = kwargs.get('verbose_name', '')
else:
to_field = to_field or to._meta.pk.name
kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name)
if kwargs.has_key('edit_inline_type'):
import warnings
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
kwargs['rel'] = ManyToOne(to, to_field,
num_in_admin=kwargs.pop('num_in_admin', 3),
min_num_in_admin=kwargs.pop('min_num_in_admin', None),
max_num_in_admin=kwargs.pop('max_num_in_admin', None),
num_extra_on_change=kwargs.pop('num_extra_on_change', 1),
edit_inline=kwargs.pop('edit_inline', False),
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
lookup_overrides=kwargs.pop('lookup_overrides', None),
raw_id_admin=kwargs.pop('raw_id_admin', False))
Field.__init__(self, **kwargs)
def get_manipulator_field_objs(self):
rel_field = self.rel.get_related_field()
if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
return rel_field.get_manipulator_field_objs()
else:
return [formfields.IntegerField]
def get_db_prep_save(self,value):
if value == '' or value == None:
return None
else:
return self.rel.get_related_field().get_db_prep_save(value)
def flatten_data(self, follow, obj=None):
if not obj:
# In required many-to-one fields with only one available choice,
# select that one available choice. Note: For SelectFields
# (radio_admin=False), we have to check that the length of choices
# is *2*, not 1, because SelectFields always have an initial
# "blank" value. Otherwise (radio_admin=True), we check that the
# length is 1.
if not self.blank and (not self.rel.raw_id_admin or self.choices):
choice_list = self.get_choices_default()
if self.radio_admin and len(choice_list) == 1:
return {self.attname: choice_list[0][0]}
if not self.radio_admin and len(choice_list) == 2:
return {self.attname: choice_list[1][0]}
return Field.flatten_data(self, follow, obj)
def contribute_to_related_class(self, cls, related):
rel_obj_name = related.get_method_name_part()
# Add "get_thingie" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice()
setattr(cls, 'get_%s' % rel_obj_name, curry(cls._get_related, method_name='get_object', rel_class=related.model, rel_field=related.field))
# Add "get_thingie_count" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice_count()
setattr(cls, 'get_%s_count' % rel_obj_name, curry(cls._get_related, method_name='get_count', rel_class=related.model, rel_field=related.field))
# Add "get_thingie_list" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice_list()
setattr(cls, 'get_%s_list' % rel_obj_name, curry(cls._get_related, method_name='get_list', rel_class=related.model, rel_field=related.field))
# Add "add_thingie" methods for many-to-one related objects,
# but only for related objects that are in the same app.
# EXAMPLE: Poll.add_choice()
if related.opts.app_label == cls._meta.app_label:
func = lambda self, *args, **kwargs: self._add_related(related.model, related.field, *args, **kwargs)
setattr(cls, 'add_%s' % rel_obj_name, func)
class ManyToManyField(Field):
def __init__(self, to, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
kwargs['rel'] = ManyToMany(to, kwargs.pop('singular', None),
num_in_admin=kwargs.pop('num_in_admin', 0),
related_name=kwargs.pop('related_name', None),
filter_interface=kwargs.pop('filter_interface', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
raw_id_admin=kwargs.pop('raw_id_admin', False))
if kwargs["rel"].raw_id_admin:
kwargs.setdefault("validator_list", []).append(self.isValidIDList)
Field.__init__(self, **kwargs)
def get_manipulator_field_objs(self):
if self.rel.raw_id_admin:
return [formfields.RawIdAdminField]
else:
choices = self.get_choices_default()
return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
def get_choices_default(self):
return Field.get_choices(self, include_blank=False)
def get_m2m_db_table(self, original_opts):
"Returns the name of the many-to-many 'join' table."
return '%s_%s' % (original_opts.db_table, self.name)
def isValidIDList(self, field_data, all_data):
"Validates that the value is a valid list of foreign keys"
mod = self.rel.to._meta.get_model_module()
try:
pks = map(int, field_data.split(','))
except ValueError:
# the CommaSeparatedIntegerField validator will catch this error
return
objects = mod.get_in_bulk(pks)
if len(objects) != len(pks):
badkeys = [k for k in pks if k not in objects]
raise validators.ValidationError, ngettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
"Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
'self': self.verbose_name,
'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
}
def flatten_data(self, follow, obj = None):
new_data = {}
if obj:
get_list_func = getattr(obj, 'get_%s_list' % self.rel.singular)
instance_ids = [getattr(instance, self.rel.to._meta.pk.attname) for instance in get_list_func()]
if self.rel.raw_id_admin:
new_data[self.name] = ",".join([str(id) for id in instance_ids])
else:
new_data[self.name] = instance_ids
else:
# In required many-to-many fields with only one available choice,
# select that one available choice.
if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin:
choices_list = self.get_choices_default()
if len(choices_list) == 1:
new_data[self.name] = [choices_list[0][0]]
return new_data
def contribute_to_related_class(self, cls, related):
rel_obj_name = related.get_method_name_part()
setattr(cls, 'get_%s' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_object', rel_class=related.model , rel_field=related.field))
setattr(cls, 'get_%s_count' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_count', rel_class=related.model, rel_field=related.field))
setattr(cls, 'get_%s_list' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_list', rel_class=related.model, rel_field=related.field))
if related.opts.app_label == cls._meta.app_label:
func = curry(cls._set_related_many_to_many, cls, related.field)
func.alters_data = True
setattr(cls, 'set_%s' % related.opts.module_name, func)
class OneToOneField(IntegerField):
def __init__(self, to, to_field=None, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
to_field = to_field or to._meta.pk.name
if kwargs.has_key('edit_inline_type'):
import warnings
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
kwargs['rel'] = OneToOne(to, to_field,
num_in_admin=kwargs.pop('num_in_admin', 0),
edit_inline=kwargs.pop('edit_inline', False),
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
lookup_overrides=kwargs.pop('lookup_overrides', None),
raw_id_admin=kwargs.pop('raw_id_admin', False))
kwargs['primary_key'] = True
IntegerField.__init__(self, **kwargs)
def contribute_to_related_class(self, cls, related):
rel_obj_name = related.get_method_name_part()
# Add "get_thingie" methods for one-to-one related objects.
# EXAMPLE: Place.get_restaurants_restaurant()
setattr(cls, 'get_%s' % rel_obj_name,
curry(cls._get_related, method_name='get_object',
rel_class=related.model, rel_field=related.field))
class ManyToOne:
def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
try:
to._meta
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
self.to, self.field_name = to, field_name
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
self.limit_choices_to = limit_choices_to or {}
self.lookup_overrides = lookup_overrides or {}
self.raw_id_admin = raw_id_admin
def get_related_field(self):
"Returns the Field in the 'to' object to which this relationship is tied."
return self.to._meta.get_field(self.field_name)
class ManyToMany:
def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
filter_interface=None, limit_choices_to=None, raw_id_admin=False):
self.to = to
self.singular = singular or to._meta.object_name.lower()
self.num_in_admin = num_in_admin
self.related_name = related_name
self.filter_interface = filter_interface
self.limit_choices_to = limit_choices_to or {}
self.edit_inline = False
self.raw_id_admin = raw_id_admin
assert not (self.raw_id_admin and self.filter_interface), "ManyToMany relationships may not use both raw_id_admin and filter_interface"
class OneToOne(ManyToOne):
def __init__(self, to, field_name, num_in_admin=0, edit_inline=False,
related_name=None, limit_choices_to=None, lookup_overrides=None,
raw_id_admin=False):
self.to, self.field_name = to, field_name
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
self.related_name = related_name
self.limit_choices_to = limit_choices_to or {}
self.lookup_overrides = lookup_overrides or {}
self.raw_id_admin = raw_id_admin
class BoundFieldLine(object): class BoundFieldLine(object):
def __init__(self, field_line, field_mapping, original, bound_field_class=BoundField): def __init__(self, field_line, field_mapping, original, bound_field_class=BoundField):
self.bound_fields = [field.bind(field_mapping, original, bound_field_class) for field in field_line] self.bound_fields = [field.bind(field_mapping, original, bound_field_class) for field in field_line]

View File

@ -0,0 +1,271 @@
from django.db.models.fields import Field, IntegerField
from django.utils.translation import gettext_lazy, string_concat
from django.utils.functional import curry
from django.core import formfields
# Values for Relation.edit_inline.
TABULAR, STACKED = 1, 2
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
#HACK
class SharedMethods(object):
def get_attname(self):
return '%s_id' % self.name
def get_validator_unique_lookup_type(self):
return '%s__%s__exact' % (self.name, self.rel.get_related_field().name)
class ForeignKey(SharedMethods,Field):
empty_strings_allowed = False
def __init__(self, to, to_field=None, **kwargs):
try:
to_name = to._meta.object_name.lower()
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
kwargs['verbose_name'] = kwargs.get('verbose_name', '')
else:
to_field = to_field or to._meta.pk.name
kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name)
if kwargs.has_key('edit_inline_type'):
import warnings
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
kwargs['rel'] = ManyToOne(to, to_field,
num_in_admin=kwargs.pop('num_in_admin', 3),
min_num_in_admin=kwargs.pop('min_num_in_admin', None),
max_num_in_admin=kwargs.pop('max_num_in_admin', None),
num_extra_on_change=kwargs.pop('num_extra_on_change', 1),
edit_inline=kwargs.pop('edit_inline', False),
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
lookup_overrides=kwargs.pop('lookup_overrides', None),
raw_id_admin=kwargs.pop('raw_id_admin', False))
Field.__init__(self, **kwargs)
if not self.db_index:
self.db_index = True
def prepare_field_objs_and_params(self, manipulator, name_prefix):
params = {'validator_list': self.validator_list[:]}
params['member_name'] = name_prefix + self.attname
if self.rel.raw_id_admin:
field_objs = self.get_manipulator_field_objs()
params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
else:
if self.radio_admin:
field_objs = [formfields.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin)
else:
if self.null:
field_objs = [formfields.NullSelectField]
else:
field_objs = [formfields.SelectField]
params['choices'] = self.get_choices_default()
return (field_objs,params)
def get_manipulator_field_objs(self):
rel_field = self.rel.get_related_field()
if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
return rel_field.get_manipulator_field_objs()
else:
return [formfields.IntegerField]
def get_db_prep_save(self,value):
if value == '' or value == None:
return None
else:
return self.rel.get_related_field().get_db_prep_save(value)
def flatten_data(self, follow, obj=None):
if not obj:
# In required many-to-one fields with only one available choice,
# select that one available choice. Note: For SelectFields
# (radio_admin=False), we have to check that the length of choices
# is *2*, not 1, because SelectFields always have an initial
# "blank" value. Otherwise (radio_admin=True), we check that the
# length is 1.
if not self.blank and (not self.rel.raw_id_admin or self.choices):
choice_list = self.get_choices_default()
if self.radio_admin and len(choice_list) == 1:
return {self.attname: choice_list[0][0]}
if not self.radio_admin and len(choice_list) == 2:
return {self.attname: choice_list[1][0]}
return Field.flatten_data(self, follow, obj)
def contribute_to_related_class(self, cls, related):
rel_obj_name = related.get_method_name_part()
# Add "get_thingie" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice()
setattr(cls, 'get_%s' % rel_obj_name, curry(cls._get_related, method_name='get_object', rel_class=related.model, rel_field=related.field))
# Add "get_thingie_count" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice_count()
setattr(cls, 'get_%s_count' % rel_obj_name, curry(cls._get_related, method_name='get_count', rel_class=related.model, rel_field=related.field))
# Add "get_thingie_list" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice_list()
setattr(cls, 'get_%s_list' % rel_obj_name, curry(cls._get_related, method_name='get_list', rel_class=related.model, rel_field=related.field))
# Add "add_thingie" methods for many-to-one related objects,
# but only for related objects that are in the same app.
# EXAMPLE: Poll.add_choice()
if related.opts.app_label == cls._meta.app_label:
func = lambda self, *args, **kwargs: self._add_related(related.model, related.field, *args, **kwargs)
setattr(cls, 'add_%s' % rel_obj_name, func)
class OneToOneField(SharedMethods, IntegerField):
def __init__(self, to, to_field=None, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
to_field = to_field or to._meta.pk.name
if kwargs.has_key('edit_inline_type'):
import warnings
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
kwargs['rel'] = OneToOne(to, to_field,
num_in_admin=kwargs.pop('num_in_admin', 0),
edit_inline=kwargs.pop('edit_inline', False),
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
lookup_overrides=kwargs.pop('lookup_overrides', None),
raw_id_admin=kwargs.pop('raw_id_admin', False))
kwargs['primary_key'] = True
IntegerField.__init__(self, **kwargs)
if not self.db_index:
self.db_index = True
def contribute_to_related_class(self, cls, related):
rel_obj_name = related.get_method_name_part()
# Add "get_thingie" methods for one-to-one related objects.
# EXAMPLE: Place.get_restaurants_restaurant()
setattr(cls, 'get_%s' % rel_obj_name,
curry(cls._get_related, method_name='get_object',
rel_class=related.model, rel_field=related.field))
class ManyToManyField(Field):
def __init__(self, to, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
kwargs['rel'] = ManyToMany(to, kwargs.pop('singular', None),
num_in_admin=kwargs.pop('num_in_admin', 0),
related_name=kwargs.pop('related_name', None),
filter_interface=kwargs.pop('filter_interface', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
raw_id_admin=kwargs.pop('raw_id_admin', False))
if kwargs["rel"].raw_id_admin:
kwargs.setdefault("validator_list", []).append(self.isValidIDList)
Field.__init__(self, **kwargs)
if self.rel.raw_id_admin:
msg = gettext_lazy(' Separate multiple IDs with commas.')
else:
msg = gettext_lazy(' Hold down "Control", or "Command" on a Mac, to select more than one.')
self.help_text = string_concat( self.help_text , msg )
def get_manipulator_field_objs(self):
if self.rel.raw_id_admin:
return [formfields.RawIdAdminField]
else:
choices = self.get_choices_default()
return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
def get_choices_default(self):
return Field.get_choices(self, include_blank=False)
def get_m2m_db_table(self, original_opts):
"Returns the name of the many-to-many 'join' table."
return '%s_%s' % (original_opts.db_table, self.name)
def isValidIDList(self, field_data, all_data):
"Validates that the value is a valid list of foreign keys"
mod = self.rel.to._meta.get_model_module()
try:
pks = map(int, field_data.split(','))
except ValueError:
# the CommaSeparatedIntegerField validator will catch this error
return
objects = mod.get_in_bulk(pks)
if len(objects) != len(pks):
badkeys = [k for k in pks if k not in objects]
raise validators.ValidationError, ngettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
"Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
'self': self.verbose_name,
'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
}
def flatten_data(self, follow, obj = None):
new_data = {}
if obj:
get_list_func = getattr(obj, 'get_%s_list' % self.rel.singular)
instance_ids = [getattr(instance, self.rel.to._meta.pk.attname) for instance in get_list_func()]
if self.rel.raw_id_admin:
new_data[self.name] = ",".join([str(id) for id in instance_ids])
else:
new_data[self.name] = instance_ids
else:
# In required many-to-many fields with only one available choice,
# select that one available choice.
if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin:
choices_list = self.get_choices_default()
if len(choices_list) == 1:
new_data[self.name] = [choices_list[0][0]]
return new_data
def contribute_to_related_class(self, cls, related):
rel_obj_name = related.get_method_name_part()
setattr(cls, 'get_%s' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_object', rel_class=related.model , rel_field=related.field))
setattr(cls, 'get_%s_count' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_count', rel_class=related.model, rel_field=related.field))
setattr(cls, 'get_%s_list' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_list', rel_class=related.model, rel_field=related.field))
if related.opts.app_label == cls._meta.app_label:
func = curry(cls._set_related_many_to_many, cls, related.field)
func.alters_data = True
setattr(cls, 'set_%s' % related.opts.module_name, func)
class ManyToOne:
def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
try:
to._meta
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
self.to, self.field_name = to, field_name
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
self.limit_choices_to = limit_choices_to or {}
self.lookup_overrides = lookup_overrides or {}
self.raw_id_admin = raw_id_admin
def get_related_field(self):
"Returns the Field in the 'to' object to which this relationship is tied."
return self.to._meta.get_field(self.field_name)
class OneToOne(ManyToOne):
def __init__(self, to, field_name, num_in_admin=0, edit_inline=False,
related_name=None, limit_choices_to=None, lookup_overrides=None,
raw_id_admin=False):
self.to, self.field_name = to, field_name
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
self.related_name = related_name
self.limit_choices_to = limit_choices_to or {}
self.lookup_overrides = lookup_overrides or {}
self.raw_id_admin = raw_id_admin
class ManyToMany:
def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
filter_interface=None, limit_choices_to=None, raw_id_admin=False):
self.to = to
self.singular = singular or to._meta.object_name.lower()
self.num_in_admin = num_in_admin
self.related_name = related_name
self.filter_interface = filter_interface
self.limit_choices_to = limit_choices_to or {}
self.edit_inline = False
self.raw_id_admin = raw_id_admin
assert not (self.raw_id_admin and self.filter_interface), "ManyToMany relationships may not use both raw_id_admin and filter_interface"

View File

@ -1,5 +1,5 @@
from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject
from django.db.models.fields import OneToOne, ManyToMany from django.db.models.fields.related import OneToOne, ManyToMany
from django.db.models.fields import AutoField from django.db.models.fields import AutoField
from django.db.models.loading import get_installed_model_modules from django.db.models.loading import get_installed_model_modules
from django.db.models.query import orderlist2sql from django.db.models.query import orderlist2sql

View File

@ -457,3 +457,13 @@ def templateize(src):
else: else:
out.write(blankout(t.contents, 'X')) out.write(blankout(t.contents, 'X'))
return out.getvalue() return out.getvalue()
def string_concat(*strings):
""""
lazy variant of string concatenation, needed for translations that are
constructed from multiple parts. Handles lazy strings and non-strings by
first turning all arguments to strings, before joining them.
"""
return ''.join([str(el) for el in strings])
string_concat = lazy(string_concat, str)