Как получить доступ к обоим направлениям ManyToManyField в Django Admin?

Настройка Django admin filter_horizontal дает хороший виджет для редактирования отношения "многие ко многим". Но это специальный параметр, который хочет список полей, поэтому он доступен только для модели (admin for), которая определяет ManyToManyField; как я могу получить тот же виджет на (админе для) другой модели, считая отношения назад?

Мои модели выглядят так (не стесняйтесь игнорировать сложность User/UserProfile, это реальный вариант использования):

class Site(models.Model):
    pass
class UserProfile(models.Model):
    user = models.OneToOneField(to=User,unique=True)
    sites = models.ManyToManyField(Site,blank=True)

Я могу получить хороший виджет в форме администратора для UserProfile с помощью

filter_horizontal = ('sites',)

но не может увидеть, как получить эквивалент в Site admin.

Я также могу получить частичный путь, добавив строку в строку SiteAdmin, определенную как:

class SiteAccessInline(admin_module.TabularInline):
    model = UserProfile.sites.through

Это круговое и неудобное; виджет не совсем интуитивно понятен для простого управления отношениями "многие ко многим".

Наконец, здесь описан трюк который включает определение другого ManyToManyField на Site и удостоверение, что он указывает на ту же таблицу базы данных (и прыгает через некоторые обручи, потому что Django на самом деле не предназначен для разных полей на разных моделях, описывающих одни и те же данные). Я надеюсь, что кто-то может показать мне что-то более чистое.

Ответ 1

Здесь (более или менее) аккуратное решение, благодаря http://blog.abiss.gr/mgogoulos/entry/many_to_many_relationships_and и с исправлением ошибки Django, взятой из http://code.djangoproject.com/ticket/5247

from django.contrib import admin as admin_module

class SiteForm(ModelForm):
    user_profiles = forms.ModelMultipleChoiceField(
        label='Users granted access',
        queryset=UserProfile.objects.all(),
        required=False,
        help_text='Admin users (who can access everything) not listed separately',
        widget=admin_module.widgets.FilteredSelectMultiple('user profiles', False))

class SiteAdmin(admin_module.ModelAdmin):
    fields = ('user_profiles',)

    def save_model(self, request, obj, form, change):
        # save without m2m field (can't save them until obj has id)
        super(SiteAdmin, self).save_model(request, obj, form, change) 
        # if that worked, deal with m2m field
        obj.user_profiles.clear()
        for user_profile in form.cleaned_data['user_profiles']:
             obj.user_profiles.add(user_profile)

    def get_form(self, request, obj=None, **kwargs):
        if obj:
            self.form.base_fields['user_profiles'].initial = [ o.pk for o in obj.userprofile_set.all() ]
        else:
            self.form.base_fields['user_profiles'].initial = []
        return super(SiteAdmin, self).get_form(request, obj, **kwargs)

Здесь используется тот же виджетов, что и параметр filter_horizontal, но жестко закодированный в форме.