Django: AJAX ManyToManyField в админке - PullRequest
       28

Django: AJAX ManyToManyField в админке

5 голосов
/ 13 февраля 2011

Я хочу отобразить ManyToManyField s в админке так же, как filter_horizontal, но заполняет опции, когда пользователь вводит в поле фильтра. Есть много вариантов, и загрузка их всех занимает много времени.

Я нашел django-ajax-Filter-fields , но мне кажется это излишним, так как требует изменений в классах модели, когда все, что я хочу сделать, это заменить каждое поле множественного выбора в форме.

Написание поля пользовательского виджета, которое наследуется от admin.widgets.FilteredSelectMultiple, кажется правильным способом. Поэтому я пытаюсь свернуть свой собственный виджет:

class MultiSelectWidget(FilteredSelectMultiple):
    class Media:
        # here should be some js to load options dynamically
        js = (
            "some_js_to_load_ajax_options.js",
        )

    def render_options(self, choices, selected_choices):
        # this initializes the multiple select without any options
        choices = [c for c in self.choices if str(c[0]) in selected_choices]
        self.choices = choices
        return super(MultiSelectWidget, 
                     self).render_options([], selected_choices)

class MyAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(MyAdminForm, self).__init__(*args, **kwargs)
        self.fields['m2m_field'].widget = MultiSelectWidget('m2m_field', is_stacked=False)
    class Meta:
        model = MyModel

class MyAdmin(admin.ModelAdmin):
    form = MyAdminForm

, который правильно отображает.

Но я не уверен, как реализовать эту some_js_to_load_ajax_options.js часть ajax. Должен ли я написать свой собственный фрагмент jQuery или изменить SelectFilter2, который идет с admin/media/js? Кто-нибудь был там раньше?

редактировать: Хотя это и не относится к сути вопроса, поскольку я хочу переопределить только виджет поля, более короткий путь заключается в использовании formfield_overrides:

class MultiSelectWidget(FilteredSelectMultiple):
    # as above

class MyAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.ManyToManyField: {'widget': MultiSelectWidget},
    }

Ответы [ 3 ]

4 голосов
/ 16 июля 2012

Я начал с вашего кода и использовал пользовательский javascript для извлечения значений из фотомодели Photo model;обратите внимание, что я использую grappelli и URL Django, которые получают объект json, жестко закодированы;также поле в моей модели называется "фотографиями":

# urls.py
url(r'^get_json_photos/(?P<query>[\w-]+)/$', 'catalogo.views.get_json_photos', name='get_json_photos'),


# views.py    
from photologue.models import Photo
from django.utils import simplejson as json

def get_json_photos(request, query):
    photos = Photo.objects.filter(title__icontains=query)[:20]
    p = [ {"name":photo.title, "id":photo.id} for photo in photos ]
    response = json.dumps(p)
    return HttpResponse(response, mimetype="application/json")


# admin.py
from django.conf import settings
from django.contrib.admin.widgets import FilteredSelectMultiple

class MyFilteredSelectMultiple(FilteredSelectMultiple):

    class Media:
        js = (settings.ADMIN_MEDIA_PREFIX + "js/core.js",
              settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js",
              settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js",
              settings.MEDIA_URL + "js/ajax_photo_list.js")


class MyModelMultipleChoiceField(ModelMultipleChoiceField):

    def clean(self, value):
        return [val for val in value]


class GalleryForm(forms.ModelForm):
    photos = MyModelMultipleChoiceField(queryset=Photo.objects.none(), required=False,
        widget=MyFilteredSelectMultiple(verbose_name="photos", is_stacked=False))

    def __init__(self, *args, **kwargs):
        super(GalleryForm, self).__init__(*args, **kwargs)
        try:
            i = kwargs["instance"]
            gallery = Gallery.objects.get(pk=i.pk)
            qs = gallery.photos.all()
        except:
            qs = Photo.objects.none()
        self.fields['photos'].queryset = qs

    class Meta:
        model = Gallery
        widgets = {
            'photos': MyFilteredSelectMultiple(verbose_name="photos", is_stacked=False)
        }


class GalleryAdmin(admin.ModelAdmin):
    list_display = ('title', 'date_added', 'photo_count', 'is_public')
    list_filter = ['date_added', 'is_public']
    date_hierarchy = 'date_added'
    prepopulated_fields = {'title_slug': ('title',)}
    filter_horizontal = ()
    form = GalleryForm


# ajax_photo_list.js 
(function($){
$("#id_photos_input").live("keyup", function(){
    var querystring = $("#id_photos_input").val();
    if (querystring) {
        $.ajax ({
            type: "GET",
            url: "/get_json_photos/"+querystring+"/",
            cache: false,
            success: function(json) {
                if (json) {
                    var list_from = $("#id_photos_from option").map(function() {
                        return parseInt($(this).val());
                    });
                    var list_to = $("#id_photos_to option").map(function() {
                        return parseInt($(this).val());
                    });
                    for (var pid in json) {
                        if ($.inArray(json[pid].id, list_from) == -1 && $.inArray(json[pid].id, list_to) == -1) {
                            $("#id_photos_from").prepend("<option value='"+json[pid].id+"'>"+json[pid].name+"</option>");
                        }
                    }
                    SelectBox.init('id_photos_from');
                    SelectBox.init('id_photos_to');
                }
            }
        });
    }
})
}(django.jQuery));

Я думаю сделать его общим, поскольку у меня не первая проблема,

0 голосов
/ 27 июля 2016

Если вам нравится пользовательский интерфейс Select2 , вы можете использовать Django-Select2 в Admin.

Для m2m это может работать так, как вы предлагали:

class MyAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.ManyToManyField: {'widget': ModelSelect2MultipleWidget},
    }

    # required to make jquery available to select2
    # has to be loaded via Admin class (and not via widget or form class) for correct order in output
    class Media:
        js = ("ext/js/jquery.min.js",)

Ajax работает, добавляя следующий шаблон URL в urls.py:

# if using ModelWidget
url(r'^select2/', include('django_select2.urls')),

Конечно, вы также можете предоставить свои собственные реализации представлений, см. Документацию, связанную выше.

В настоящее время я использую его не для m2m, а для обратных отношений с внешним ключом, поэтому я использую его в пользовательской форме в администраторе Django, явно создавая экземпляр виджета.Таким образом, в случае, если он не работает с formfield_overrides, длинный путь был бы вариантом.

0 голосов
/ 19 июня 2011

Я бы взломал фильтр выбора, у него есть хороший набор функций, которые вы можете использовать.

...