diff options
| author | Adrian Holovaty <adrian@holovaty.com> | 2007-01-25 18:05:10 +0000 |
|---|---|---|
| committer | Adrian Holovaty <adrian@holovaty.com> | 2007-01-25 18:05:10 +0000 |
| commit | 39e63513a8e74716d8fd98c23b6301e824c8e042 (patch) | |
| tree | 2083d215045bde564b40808bb3faa549295a9a8a | |
| parent | 12ad69dbaafa0f3cac89c0703b97dd15fc27f5ef (diff) | |
newforms-admin: Backwards-incompatible change: Refactored raw_id_admin. It is no longer a valid attribute for fields. Instead, specify 'raw_id_fields', a list of field names, in the 'class Admin'. Also removed raw_id functionality for ManyToManyFields, as it was hackish.
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@4430 bcc190cf-cafb-0310-a4f2-bffc1f526a37
| -rw-r--r-- | django/contrib/admin/media/js/admin/RelatedObjectLookups.js | 2 | ||||
| -rw-r--r-- | django/contrib/admin/options.py | 17 | ||||
| -rw-r--r-- | django/contrib/admin/widgets.py | 43 | ||||
| -rw-r--r-- | django/contrib/comments/models.py | 3 | ||||
| -rw-r--r-- | django/db/models/fields/generic.py | 43 | ||||
| -rw-r--r-- | django/db/models/fields/related.py | 83 | ||||
| -rw-r--r-- | django/db/models/manipulators.py | 7 |
7 files changed, 91 insertions, 107 deletions
diff --git a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js index db4ed1a9d1..f50ff43c91 100644 --- a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js +++ b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js @@ -1,4 +1,4 @@ -// Handles related-objects functionality: lookup link for raw_id_admin=True +// Handles related-objects functionality: lookup link for raw_id_fields // and Add Another links. function showRelatedObjectLookupPopup(triggeringLink) { diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index e681e23440..946e074624 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -108,6 +108,7 @@ class ModelAdmin(object): ordering = None js = None fields = None + raw_id_fields = () def __init__(self, model): self.model = model @@ -224,12 +225,16 @@ class ModelAdmin(object): return db_field.formfield(**kwargs) # For ForeignKey or ManyToManyFields, use a special widget. - if db_field.rel and isinstance(db_field.rel, (models.ManyToOneRel, models.ManyToManyRel)): - # Wrap the widget's render() method with a method that adds - # extra HTML to the end of the rendered output. - formfield = db_field.formfield(**kwargs) - formfield.widget.render = widgets.RelatedFieldWidgetWrapper(formfield.widget.render, db_field.rel) - return formfield + if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)): + if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields: + kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel) + return db_field.formfield(**kwargs) + else: + # Wrap the widget's render() method with a method that adds + # extra HTML to the end of the rendered output. + formfield = db_field.formfield(**kwargs) + formfield.widget.render = widgets.RelatedFieldWidgetWrapper(formfield.widget.render, db_field.rel) + return formfield # For any other type of field, just call its formfield() method. return db_field.formfield(**kwargs) diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 1a2abe85ce..62c6a450b7 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -3,6 +3,8 @@ Form Widget classes specific to the Django admin site. """ from django import newforms as forms +from django.newforms.util import smart_unicode +from django.utils.text import capfirst class FilteredSelectMultiple(forms.SelectMultiple): """ @@ -41,6 +43,33 @@ class AdminSplitDateTime(forms.SplitDateTimeWidget): return u'<p class="datetime">%s %s<br />%s %s</p>' % \ (_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1]) +class ForeignKeyRawIdWidget(forms.TextInput): + """ + A Widget for displaying ForeignKeys in the "raw_id" interface rather than + in a <select> box. + """ + def __init__(self, rel, attrs=None): + self.rel = rel + super(ForeignKeyRawIdWidget, self).__init__(attrs) + + def render(self, name, value, attrs=None): + from django.conf import settings + related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower()) + if self.rel.limit_choices_to: + url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in self.rel.limit_choices_to.items()]) + else: + url = '' + attrs['class'] = 'vRawIdAdminField' # The JavaScript looks for this hook. + output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + # TODO: "id_" is hard-coded here. This should instead use the correct + # API to determine the ID dynamically. + output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \ + (related_url, url, name)) + output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>' % settings.ADMIN_MEDIA_PREFIX) + return u''.join(output) + #if self.change: # TODO + #output.append(' <strong>TODO</strong>') + class RelatedFieldWidgetWrapper(object): """ This class is a wrapper whose __call__() method mimics the interface of a @@ -54,19 +83,7 @@ class RelatedFieldWidgetWrapper(object): rel_to = self.rel.to related_url = '../../../%s/%s/' % (rel_to._meta.app_label, rel_to._meta.object_name.lower()) output = [self.render_func(name, value, *args, **kwargs)] - if self.rel.raw_id_admin: - if self.rel.limit_choices_to: - url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in self.rel.limit_choices_to.items()]) - else: - url = '' - # TODO: "id_" is hard-coded here. This should instead use the correct - # API to determine the ID dynamically. - output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \ - (related_url, url, name)) - output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>' % settings.ADMIN_MEDIA_PREFIX) - #if self.change: # TODO - #output.append(' <strong>TODO</strong>') - elif rel_to._meta.admin: # If the related object has an admin interface: + if rel_to._meta.admin: # If the related object has an admin interface: # TODO: "id_" is hard-coded here. This should instead use the correct # API to determine the ID dynamically. output.append(u'<a href="%sadd/" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \ diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py index 90a84baaff..4517f0d265 100644 --- a/django/contrib/comments/models.py +++ b/django/contrib/comments/models.py @@ -62,7 +62,7 @@ class CommentManager(models.Manager): return False class Comment(models.Model): - user = models.ForeignKey(User, raw_id_admin=True) + user = models.ForeignKey(User) content_type = models.ForeignKey(ContentType) object_id = models.IntegerField(_('object ID')) headline = models.CharField(_('headline'), maxlength=255, blank=True) @@ -101,6 +101,7 @@ class Comment(models.Model): list_filter = ('submit_date',) date_hierarchy = 'submit_date' search_fields = ('comment', 'user__username') + raw_id_fields = ('user',) def __repr__(self): return "%s: %s..." % (self.user.username, self.comment[:100]) diff --git a/django/db/models/fields/generic.py b/django/db/models/fields/generic.py index 1ad8346e42..96ae61a657 100644 --- a/django/db/models/fields/generic.py +++ b/django/db/models/fields/generic.py @@ -16,18 +16,18 @@ class GenericForeignKey(object): Provides a generic relation to any object through content-type/object-id fields. """ - + def __init__(self, ct_field="content_type", fk_field="object_id"): self.ct_field = ct_field self.fk_field = fk_field - + def contribute_to_class(self, cls, name): - # Make sure the fields exist (these raise FieldDoesNotExist, + # Make sure the fields exist (these raise FieldDoesNotExist, # which is a fine error to raise here) self.name = name self.model = cls self.cache_attr = "_%s_cache" % name - + # For some reason I don't totally understand, using weakrefs here doesn't work. dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False) @@ -35,18 +35,18 @@ class GenericForeignKey(object): setattr(cls, name, self) def instance_pre_init(self, signal, sender, args, kwargs): - # Handle initalizing an object with the generic FK instaed of - # content-type/object-id fields. + # Handle initalizing an object with the generic FK instaed of + # content-type/object-id fields. if kwargs.has_key(self.name): value = kwargs.pop(self.name) kwargs[self.ct_field] = self.get_content_type(value) kwargs[self.fk_field] = value._get_pk_val() - + def get_content_type(self, obj): # Convenience function using get_model avoids a circular import when using this model ContentType = get_model("contenttypes", "contenttype") return ContentType.objects.get_for_model(obj) - + def __get__(self, instance, instance_type=None): if instance is None: raise AttributeError, "%s must be accessed via instance" % self.name @@ -77,21 +77,21 @@ class GenericForeignKey(object): setattr(instance, self.ct_field, ct) setattr(instance, self.fk_field, fk) setattr(instance, self.cache_attr, value) - + class GenericRelation(RelatedField, Field): """Provides an accessor to generic related objects (i.e. comments)""" def __init__(self, to, **kwargs): kwargs['verbose_name'] = kwargs.get('verbose_name', None) - kwargs['rel'] = GenericRel(to, + kwargs['rel'] = GenericRel(to, related_name=kwargs.pop('related_name', None), limit_choices_to=kwargs.pop('limit_choices_to', None), symmetrical=kwargs.pop('symmetrical', True)) - + # Override content-type/object-id field names on the related class self.object_id_field_name = kwargs.pop("object_id_field", "object_id") - self.content_type_field_name = kwargs.pop("content_type_field", "content_type") - + self.content_type_field_name = kwargs.pop("content_type_field", "content_type") + kwargs['blank'] = True kwargs['editable'] = False Field.__init__(self, **kwargs) @@ -115,7 +115,7 @@ class GenericRelation(RelatedField, Field): def m2m_column_name(self): return self.object_id_field_name - + def m2m_reverse_name(self): return self.object_id_field_name @@ -130,13 +130,13 @@ class GenericRelation(RelatedField, Field): def contribute_to_related_class(self, cls, related): pass - + def set_attributes_from_rel(self): pass def get_internal_type(self): return "ManyToManyField" - + class ReverseGenericRelatedObjectsDescriptor(object): """ This class provides the functionality that makes the related-object @@ -190,12 +190,12 @@ def create_generic_related_manager(superclass): Factory function for a manager that subclasses 'superclass' (which is a Manager) and adds behavior for generic related objects. """ - + class GenericRelatedObjectManager(superclass): def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, join_table=None, source_col_name=None, target_col_name=None, content_type=None, content_type_field_name=None, object_id_field_name=None): - + super(GenericRelatedObjectManager, self).__init__() self.core_filters = core_filters or {} self.model = model @@ -209,10 +209,10 @@ def create_generic_related_manager(superclass): self.content_type_field_name = content_type_field_name self.object_id_field_name = object_id_field_name self.pk_val = self.instance._get_pk_val() - + def get_query_set(self): query = { - '%s__pk' % self.content_type_field_name : self.content_type.id, + '%s__pk' % self.content_type_field_name : self.content_type.id, '%s__exact' % self.object_id_field_name : self.pk_val, } return superclass.get_query_set(self).filter(**query) @@ -252,8 +252,5 @@ class GenericRel(ManyToManyRel): self.filter_interface = None self.limit_choices_to = limit_choices_to or {} self.edit_inline = False - self.raw_id_admin = False self.symmetrical = symmetrical self.multiple = True - assert not (self.raw_id_admin and self.filter_interface), \ - "Generic relations may not use both raw_id_admin and filter_interface" diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index ef5432686b..ddb99c90a4 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -483,8 +483,7 @@ class ForeignKey(RelatedField, Field): 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)) + lookup_overrides=kwargs.pop('lookup_overrides', None)) Field.__init__(self, **kwargs) self.db_index = True @@ -497,27 +496,20 @@ class ForeignKey(RelatedField, Field): def prepare_field_objs_and_params(self, manipulator, name_prefix): params = {'validator_list': self.validator_list[:], '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)) + if self.radio_admin: + field_objs = [oldforms.RadioSelectField] + params['ul_class'] = get_ul_class(self.radio_admin) else: - if self.radio_admin: - field_objs = [oldforms.RadioSelectField] - params['ul_class'] = get_ul_class(self.radio_admin) + if self.null: + field_objs = [oldforms.NullSelectField] else: - if self.null: - field_objs = [oldforms.NullSelectField] - else: - field_objs = [oldforms.SelectField] - params['choices'] = self.get_choices_default() + field_objs = [oldforms.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 [oldforms.IntegerField] + return [oldforms.IntegerField] def get_db_prep_save(self, value): if value == '' or value == None: @@ -533,7 +525,7 @@ class ForeignKey(RelatedField, Field): # 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): + if not self.blank and self.choices: choice_list = self.get_choices_default() if self.radio_admin and len(choice_list) == 1: return {self.attname: choice_list[0][0]} @@ -573,8 +565,7 @@ class OneToOneField(RelatedField, IntegerField): 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)) + lookup_overrides=kwargs.pop('lookup_overrides', None)) kwargs['primary_key'] = True IntegerField.__init__(self, **kwargs) @@ -590,19 +581,15 @@ class OneToOneField(RelatedField, IntegerField): # ManyToManyField. This works for now. def prepare_field_objs_and_params(self, manipulator, name_prefix): params = {'validator_list': self.validator_list[:], '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)) + if self.radio_admin: + field_objs = [oldforms.RadioSelectField] + params['ul_class'] = get_ul_class(self.radio_admin) else: - if self.radio_admin: - field_objs = [oldforms.RadioSelectField] - params['ul_class'] = get_ul_class(self.radio_admin) + if self.null: + field_objs = [oldforms.NullSelectField] else: - if self.null: - field_objs = [oldforms.NullSelectField] - else: - field_objs = [oldforms.SelectField] - params['choices'] = self.get_choices_default() + field_objs = [oldforms.SelectField] + params['choices'] = self.get_choices_default() return field_objs, params def contribute_to_class(self, cls, name): @@ -627,24 +614,15 @@ class ManyToManyField(RelatedField, Field): 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), symmetrical=kwargs.pop('symmetrical', True)) - 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.') + 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 [oldforms.RawIdAdminField] - else: - choices = self.get_choices_default() - return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] + choices = self.get_choices_default() + return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] def get_choices_default(self): return Field.get_choices(self, include_blank=False) @@ -690,14 +668,11 @@ class ManyToManyField(RelatedField, Field): new_data = {} if obj: instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()] - 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 + 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: + if not self.blank and not self.rel.edit_inline: choices_list = self.get_choices_default() if len(choices_list) == 1: new_data[self.name] = [choices_list[0][0]] @@ -741,7 +716,7 @@ class ManyToManyField(RelatedField, Field): class ManyToOneRel(object): 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): + related_name=None, limit_choices_to=None, lookup_overrides=None): try: to._meta except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT @@ -754,7 +729,6 @@ class ManyToOneRel(object): limit_choices_to = {} self.limit_choices_to = limit_choices_to self.lookup_overrides = lookup_overrides or {} - self.raw_id_admin = raw_id_admin self.multiple = True def get_related_field(self): @@ -763,8 +737,7 @@ class ManyToOneRel(object): class OneToOneRel(ManyToOneRel): 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): + related_name=None, limit_choices_to=None, lookup_overrides=None): 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 @@ -772,12 +745,11 @@ class OneToOneRel(ManyToOneRel): limit_choices_to = {} self.limit_choices_to = limit_choices_to self.lookup_overrides = lookup_overrides or {} - self.raw_id_admin = raw_id_admin self.multiple = False class ManyToManyRel(object): def __init__(self, to, num_in_admin=0, related_name=None, - filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True): + filter_interface=None, limit_choices_to=None, symmetrical=True): self.to = to self.num_in_admin = num_in_admin self.related_name = related_name @@ -786,8 +758,5 @@ class ManyToManyRel(object): limit_choices_to = {} self.limit_choices_to = limit_choices_to self.edit_inline = False - self.raw_id_admin = raw_id_admin self.symmetrical = symmetrical self.multiple = True - - assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface" diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py index aea3aa70a7..c3b63ca0ae 100644 --- a/django/db/models/manipulators.py +++ b/django/db/models/manipulators.py @@ -116,10 +116,7 @@ class AutomaticManipulator(oldforms.Manipulator): for f in self.opts.many_to_many: if self.follow.get(f.name, None): if not f.rel.edit_inline: - if f.rel.raw_id_admin: - new_vals = new_data.get(f.name, ()) - else: - new_vals = new_data.getlist(f.name) + new_vals = new_data.getlist(f.name) # First, clear the existing values. rel_manager = getattr(new_object, f.name) rel_manager.clear() @@ -216,8 +213,6 @@ class AutomaticManipulator(oldforms.Manipulator): for f in related.opts.many_to_many: if child_follow.get(f.name, None) and not f.rel.edit_inline: new_value = rel_new_data[f.attname] - if f.rel.raw_id_admin: - new_value = new_value[0] setattr(new_rel_obj, f.name, f.rel.to.objects.filter(pk__in=new_value)) if self.change: self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj)) |
