2011-02-13 50 views
4

我想在管理員中顯示ManyToManyField s,就像filter_horizontal一樣,但在用戶鍵入過濾器字段時填充選項。有很多選項,一次加載它們需要很多時間。Django:AJAX ManyToManyField in admin

我發現django-ajax-filtered-fields但在我看來,這是一個矯枉過正的事情,因爲它需要對模型類進行更改,當我想要做的就是替換表單中的每個多個選擇字段。

編寫從admin.widgets.FilteredSelectMultiple繼承的自定義控件字段似乎是正確的方法。所以我試圖推出我自己的widget:

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}, 
    } 

回答

0

我就砍了選擇濾波器,它有一個很好的功能,你可以使用。

+1

你能更具體嗎? – omat 2011-06-20 06:50:22

4

我從你的代碼開始,我用一個自定義的javascript從photologue Photo模型檢索值;請注意,我使用的是grappelli,並且獲取json對象的Django url是硬編碼的;也是在我的模型領域被稱爲「照片」:

# 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

對於我添加到我的ajax支持的選擇框'code`SelectBox.init('id_photos_from'); SelectBox.init('id_photos_to');`code` – elsadek 2014-10-06 20:08:59

0

如果Select2上訴UI對於您,您可以在管理員中使用Django-Select2

對於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,那麼漫長的路將是一個選擇。