Django: AJAX ManyToManyField in admin

I want to display ManyToManyField in admin, as filter_horizontal does, but populates the parameters when the user enters a filter field. There are many options, and downloading them immediately takes a lot of time.

I found django-ajax-filtered-fields , but it seems to me that it is too complicated, because it requires changes in the model classes, when all I want to do is replace each several selection fields in the form.

Writing a custom widget field that inherits from admin.widgets.FilteredSelectMultiple seems to be correct. So I am trying to launch my own 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 

which is displayed correctly.

But I'm not sure how to implement this some_js_to_load_ajax_options.js ajax part. Should I write my own jQuery snippet or modify the SelectFilter2 that comes with admin/media/js ? Has anyone been there before?

edit Although this is not related to the core of the question, since I only want to override the field widget, a shorter way is to use formfield_overrides :

 class MultiSelectWidget(FilteredSelectMultiple): # as above class MyAdmin(admin.ModelAdmin): formfield_overrides = { models.ManyToManyField: {'widget': MultiSelectWidget}, } 
+4
source share
3 answers

I started with your code, and I used my own javascript to extract the values ​​from the Photo model; note that I use grappelli and a Django url that force the json object to hardcode; also the field in my model is called β€œphotos”:

 # 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)); 

I am going to make it general, as this is not the first time that I have this problem,

+4
source

I would hack the selection filter, it has a good set of functions that you can use.

0
source

If the Select2 user interface appeals to you, you can use Django-Select2 in Admin.

For m2m, it can work as you suggested:

 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 works by adding the following URL pattern to urls.py :

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

Of course, you can also provide your own views, see the documentation linked above.

I am not currently using it for m2m, but for reverse foreign key relationships, so I use it in a user form in the Django admin, explicitly creating a widget. So in case it doesn't work with formfield_overrides , a long way would be an option.

0
source

Source: https://habr.com/ru/post/1339540/


All Articles