diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
index 2f3cb08aa3..d607594e06 100644
--- a/django/contrib/admin/templatetags/admin_modify.py
+++ b/django/contrib/admin/templatetags/admin_modify.py
@@ -5,7 +5,7 @@ from django.utils.functional import curry
from django.contrib.admin.views.main import AdminBoundField
from django.db.models.fields import BoundField, Field
from django.db.models.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.conf.settings import ADMIN_MEDIA_PREFIX
import re
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 319df83354..6ac5d7d14f 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -472,11 +472,13 @@ def log_add_message(user, opts,manipulator,new_object):
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)
-def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
- mod, opts = _get_mod_opts(app_label, module_name)
+def add_stage(request, path, show_delete=False, form_url='', post_url='../change/', post_url_continue='../%s/', object_id_override=None):
+ model, app_label = get_model_and_app(path)
+ opts = model._meta
+
if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
raise PermissionDenied
- manipulator = mod.AddManipulator()
+ manipulator = model.AddManipulator()
if request.POST:
new_data = request.POST.copy()
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:
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)
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)
def change_stage(request, path, object_id):
- print "change_stage", path, object_id
+
model, app_label = get_model_and_app(path)
opts = model._meta
#mod, opts = _get_mod_opts(app_label, module_name)
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index fe4e510472..71366f4949 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -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.manager import Manager
from django.db.models.base import Model
+
from django.db.models.fields import *
+from django.db.models.fields.related import *
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.exceptions import FieldDoesNotExist, BadKeywordArguments
+
# Admin stages.
ADD, CHANGE, BOTH = 1, 2, 3
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 5b842048da..c346db2eaa 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -1,6 +1,6 @@
from django.db.models.manipulators import ManipulatorDescriptor, ModelAddManipulator, ModelChangeManipulator
from django.db.models.fields import Field, DateField, FileField, ImageField, AutoField
-from django.db.models.fields 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.manager import Manager, ManagerDescriptor
from django.db.models.query import orderlist2sql
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index cbcb385764..fd1c77974e 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -16,10 +16,6 @@ HORIZONTAL, VERTICAL = 1, 2
BLANK_CHOICE_DASH = [("", "---------")]
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
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
class for a given radio_admin value
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):
"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):
"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)
- else:
- lookup_type = '%s__exact' % f.name
+
+ lookup_type = f.get_validator_unique_lookup_type()
+
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:
return
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
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,
prepopulate_from=None, unique_for_date=None, unique_for_month=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.help_text = help_text
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.
- if db_index is None:
- if isinstance(rel, OneToOne) or isinstance(rel, ManyToOne):
- self.db_index = True
- else:
- self.db_index = False
- else:
- self.db_index = db_index
+
+ self.db_index = db_index
# Increase the creation counter, and save our local copy.
self.creation_counter = Field.creation_counter
@@ -151,11 +126,11 @@ class Field(object):
self.verbose_name = self.verbose_name or name.replace('_', ' ')
self.attname, self.column = self.get_attname_column()
+ def get_attname(self):
+ return self.name
+
def get_attname_column(self):
- if isinstance(self.rel, ManyToOne):
- attname = '%s_id' % self.name
- else:
- attname = self.name
+ attname = self.get_attname()
column = self.db_column or attname
return attname, column
@@ -213,33 +188,12 @@ class Field(object):
"""
return [name_prefix + self.name]
- 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.
- """
+ def prepare_field_objs_and_params(self, manipulator, name_prefix):
params = {'validator_list': self.validator_list[:]}
if self.maxlength and not self.choices: # Don't give SelectFields a maxlength parameter.
params['maxlength'] = self.maxlength
- if isinstance(self.rel, ManyToOne):
- 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()
- elif self.choices:
+
+ if self.choices:
if self.radio_admin:
field_objs = [formfields.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin)
@@ -249,6 +203,17 @@ class Field(object):
params['choices'] = self.get_choices_default()
else:
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).
for field_name_list in opts.unique_together:
@@ -293,6 +258,9 @@ class Field(object):
field_names = self.get_manipulator_field_names(name_prefix)
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):
"""
Given the full new_data dictionary (from the manipulator), returns this
@@ -684,228 +652,6 @@ class XMLField(TextField):
def get_manipulator_field_objs(self):
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):
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]
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
new file mode 100644
index 0000000000..6368a8473b
--- /dev/null
+++ b/django/db/models/fields/related.py
@@ -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"
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 9b6358b184..47a60e989e 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -1,5 +1,5 @@
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.loading import get_installed_model_modules
from django.db.models.query import orderlist2sql
diff --git a/django/utils/translation.py b/django/utils/translation.py
index 9c36850fb9..9a924385d6 100644
--- a/django/utils/translation.py
+++ b/django/utils/translation.py
@@ -457,3 +457,13 @@ def templateize(src):
else:
out.write(blankout(t.contents, 'X'))
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)
\ No newline at end of file