Ticket #10964: 10964_r17068.diff

File 10964_r17068.diff, 8.4 KB (added by Koen Biermans, 7 years ago)

reverse m2m on forms + in admin with list_horizontal

  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index 3a0ad74..a1bc2f2 100644
    a b from django.core.urlresolvers import reverse 
    1515from django.db import models, transaction, router
    1616from django.db.models.related import RelatedObject
    1717from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
     18from django.db.models.fields.related import ManyRelatedObjectsDescriptor
    1819from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
    1920from django.http import Http404, HttpResponse, HttpResponseRedirect
    2021from django.shortcuts import get_object_or_404
    class ModelAdmin(BaseModelAdmin): 
    455456            "exclude": exclude,
    456457            "formfield_callback": partial(self.formfield_for_dbfield, request=request),
    457458        }
     459        if fields:
     460            revM2M_widgets = {}
     461            other_fields = set(fields) - set(self.model._meta.fields + self.model._meta.many_to_many)
     462            for item in other_fields:
     463                if hasattr(self.model, item) and isinstance(getattr(self.model, item), ManyRelatedObjectsDescriptor):
     464                    if item in (list(self.filter_vertical) + list(self.filter_horizontal)):
     465                        revM2M_widgets[item] = widgets.FilteredSelectMultiple(item, (item in self.filter_vertical))
     466            if widgets:
     467                defaults['widgets'] = revM2M_widgets
    458468        defaults.update(kwargs)
    459469        return modelform_factory(self.model, **defaults)
    460470
  • django/contrib/admin/validation.py

    diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
    index 733f89d..0787549 100644
    a b  
    11from django.core.exceptions import ImproperlyConfigured
    22from django.db import models
    33from django.db.models.fields import FieldDoesNotExist
     4from django.db.models.fields.related import ManyRelatedObjectsDescriptor
    45from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
    56    _get_foreign_key)
    67from django.contrib.admin import ListFilter, FieldListFilter
    def validate_fields_spec(cls, model, opts, flds, label): 
    246247                # readonly_fields will handle the validation of such
    247248                # things.
    248249                continue
     250            if hasattr(model, field) and isinstance(getattr(model, field), ManyRelatedObjectsDescriptor):
     251                # allow reverse M2M descriptors
     252                continue
    249253            check_formfield(cls, model, opts, label, field)
    250254            try:
    251255                f = opts.get_field(field)
    def validate_base(cls, model): 
    331335    if hasattr(cls, 'filter_horizontal'):
    332336        check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
    333337        for idx, field in enumerate(cls.filter_horizontal):
    334             f = get_field(cls, model, opts, 'filter_horizontal', field)
    335             if not isinstance(f, models.ManyToManyField):
     338            valid = False
     339            if hasattr(model, field) and isinstance(getattr(model, field), ManyRelatedObjectsDescriptor):
     340                valid = True
     341            if not valid:
     342                f = get_field(cls, model, opts, 'filter_horizontal', field)
     343                if isinstance(f, models.ManyToManyField):
     344                    valid = True
     345            if not valid:
    336346                raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
    337347                    "a ManyToManyField." % (cls.__name__, idx))
    338348
  • django/contrib/auth/admin.py

    diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
    index 3868794..a58069c 100644
    a b csrf_protect_m = method_decorator(csrf_protect) 
    1919class GroupAdmin(admin.ModelAdmin):
    2020    search_fields = ('name',)
    2121    ordering = ('name',)
    22     filter_horizontal = ('permissions',)
     22    fields = ('name', 'permissions', 'user_set')
     23    filter_horizontal = ('permissions', 'user_set')
    2324
    2425    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
    2526        if db_field.name == 'permissions':
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index b65f067..d6cc94a 100644
    a b from django.utils.datastructures import SortedDict 
    1818from django.utils.text import get_text_list, capfirst
    1919from django.utils.translation import ugettext_lazy as _, ugettext
    2020
    21 
    2221__all__ = (
    2322    'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
    2423    'save_instance', 'ModelChoiceField', 'ModelMultipleChoiceField',
    def save_instance(form, instance, fields=None, fail_message='saved', 
    8180                continue
    8281            if f.name in cleaned_data:
    8382                f.save_form_data(instance, cleaned_data[f.name])
     83        if fields:
     84            other_fields = set(fields) - set(opts.fields + opts.many_to_many)
     85            for item in other_fields:
     86                setattr(instance, item, cleaned_data[item])
     87
    8488    if commit:
    8589        # If we are committing, save the instance and the m2m data immediately.
    8690        instance.save()
    def model_to_dict(instance, fields=None, exclude=None): 
    107111    the ``fields`` argument.
    108112    """
    109113    # avoid a circular import
    110     from django.db.models.fields.related import ManyToManyField
     114    from django.db.models.fields.related import ManyToManyField, ManyRelatedObjectsDescriptor
    111115    opts = instance._meta
    112116    data = {}
    113117    for f in opts.fields + opts.many_to_many:
    def model_to_dict(instance, fields=None, exclude=None): 
    128132                data[f.name] = [obj.pk for obj in f.value_from_object(instance)]
    129133        else:
    130134            data[f.name] = f.value_from_object(instance)
     135    if fields:
     136        other_fields = set(fields) - set(data.keys())
     137        for item in other_fields:
     138            if hasattr(instance.__class__, item) and \
     139                    isinstance(getattr(instance.__class__, item), ManyRelatedObjectsDescriptor):
     140                if instance.pk is None:
     141                    data[item] = []
     142                else:
     143                    # MultipleChoiceWidget needs a list of pks, not object instances.
     144                    data[item] = [obj.pk for obj in getattr(instance, item).all()]
    131145    return data
    132146
    133147def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None):
    def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c 
    140154    ``exclude`` is an optional list of field names. If provided, the named
    141155    fields will be excluded from the returned fields, even if they are listed
    142156    in the ``fields`` argument.
    143     """
     157    """   
     158    from django.db.models.fields.related import ManyRelatedObjectsDescriptor
     159    from django.forms import ModelMultipleChoiceField
     160
    144161    field_list = []
    145162    ignored = []
    146163    opts = model._meta
    def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c 
    167184            field_list.append((f.name, formfield))
    168185        else:
    169186            ignored.append(f.name)
     187
     188    if fields:
     189        missing_fields = set(fields) - set([k[0] for k in field_list])
     190        for item in missing_fields:
     191            if hasattr(model, item) and isinstance(getattr(model, item), ManyRelatedObjectsDescriptor):
     192                kwargs = {
     193                    'required': False,
     194                    'queryset': getattr(model, item).related.model._default_manager.all()
     195                }
     196                if widgets and item in widgets:
     197                    kwargs['widget'] = widgets[item]
     198                formfield = ModelMultipleChoiceField(**kwargs)
     199                field_list.append((item, formfield))
     200
    170201    field_dict = SortedDict(field_list)
    171202    if fields:
    172203        field_dict = SortedDict(
  • tests/regressiontests/admin_views/tests.py

    diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
    index f4ec63f..42aebdb 100644
    a b class GroupAdminTest(TestCase): 
    29862986    def test_group_permission_performance(self):
    29872987        g = Group.objects.create(name="test_group")
    29882988
    2989         with self.assertNumQueries(6):  # instead of 259!
     2989        with self.assertNumQueries(8):  # instead of 259!
    29902990            response = self.client.get('/test_admin/admin/auth/group/%s/' % g.pk)
    29912991            self.assertEqual(response.status_code, 200)
    29922992
Back to Top