Django:管理中的 AJAX ManyToManyField

发布于 2024-10-16 23:18:14 字数 1776 浏览 2 评论 0原文

我想在管理中显示 ManyToManyField,就像 filter_horizo​​ntal 那样,但在用户在过滤器字段中键入内容时填充选项。有很多选项,一次加载它们需要很多时间。

我找到了 django-ajax-filtered-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 片段还是修改 admin/media/js 附带的 SelectFilter2 ?有人以前去过那里吗?

编辑: 虽然与问题的核心无关,但因为我只想覆盖字段的小部件,所以更短的方法是使用 formfield_overrides

class MultiSelectWidget(FilteredSelectMultiple):
    # as above

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

I want to display ManyToManyFields in admin just like filter_horizontal does, but populate the options as the user types into the filter field. There are many options and loading them all at once takes a lot of time.

I found django-ajax-filtered-fields but it seems to me an overkill as it requires changes to model classes, when all I want to do is to replace every multiple select field in a form.

Writing a custom widget field that inherits from admin.widgets.FilteredSelectMultiple seems to be the right way. So I am trying to roll 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 renders correctly.

But I am not sure how to implement this some_js_to_load_ajax_options.js ajax part. Should I write my own jQuery snippet or modify SelectFilter2 which comes with admin/media/js? Anybody been there before?

edit:
Although not related to the core of the question, as I only want to override the field's widget, the shorter way is to use formfield_overrides:

class MultiSelectWidget(FilteredSelectMultiple):
    # as above

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

猥琐帝 2024-10-23 23:18:14

我从你的代码开始,使用自定义 JavaScript 从 photologue 照片模型中检索值;请注意,我使用的是 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));

我正在考虑使其通用,因为这不是我第一次遇到这个问题,

I started from your code and I used a custom javascript to retrieve values from photologue Photo model; please note that I'm using grappelli and the Django url that get the json object is hardcoded; also the field in my model is called "photos":

# urls.py
url(r'^get_json_photos/(?P<query>[\w-]+)/

I'm thinking to make it generic, since is not the first time that I have this problem,

, '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'm thinking to make it generic, since is not the first time that I have this problem,

请你别敷衍 2024-10-23 23:18:14

如果 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 一起使用,长距离将是一个选择。

If the UI of Select2 appeals to you, you could use Django-Select2 in the Admin.

For m2m it might work like 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 view implementations, see the documentation linked above.

I'm currently not using it for m2m but for reverse foreign key relations, so I'm using it in a custom form in the Django admin, instantiating the widget explicitly. Thus, in case it's not working with formfield_overrides, the long way would be an option.

昇り龍 2024-10-23 23:18:14

我会破解选择过滤器,它有一组很好的功能可供您使用。

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文