Forms.ModelMultipleChoiceField с widget=FilteredSelectMultiple не работает на новом Django Admin

Я пытаюсь показать поле forms.ModelMultipleChoiceField на новой странице пользовательской формы администратора, но, похоже, оно не отображается так, как на уже созданной Django-странице, например, на странице администратора django model product. Мой forms.ModelMultipleChoiceField выглядит следующим образом: Показывает как выглядит мой forms.ModelMultipleChoiceField. Когда оно должно выглядеть так: Показывает, как должно выглядеть поле forms.ModelMultipleChoiceField

forms.py:

from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django.contrib.admin.widgets import FilteredSelectMultiple
from home.models import Collection, Tag, Product

class ProductAssignForm(forms.Form):
    from_name = forms.CharField(required=True, max_length=255, label='From Name')
    to_name = forms.CharField(required=True, max_length=255, label='To Name')

    assign_collections_name = forms.ModelMultipleChoiceField(
        queryset=Collection.objects.all(),
        required=False,
        widget=FilteredSelectMultiple(
            verbose_name='Collections',
            is_stacked=False
        ),
        label='Assign Collection Name'
    )
    tags = forms.ModelMultipleChoiceField(
        queryset=Tag.objects.all(),
        required=False,
        widget=FilteredSelectMultiple(
            verbose_name='Tags',
            is_stacked=False
        ),
        label='Tags'
    )

    class Meta:
        model = Product
        fields = ['collections', 'tags']  # Include the tags field in the fields list

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper.form_method = 'POST'
        self.helper.add_input(Submit('submit', 'Assign Products'))

частичный код формы admin.py:

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm
    list_display = ('name', 'quantity', 'price', 'first_collection')
    exclude = ('user', 'updated',)

    def save_model(self, request, obj, form, change):
        if not obj.user:
            obj.user = request.user
        obj.save()

    def first_collection(self, obj):
        first_collection = obj.collections.first()
        return first_collection.name if first_collection else 'No collection'

    def get_urls(self):
        # return [
        #     path('assign-products/', self.admin_site.admin_view(self.assign_products), name='assign-products'),
        # ] + super().get_urls()
        custom_urls = [
            path('assign-products/', self.admin_site.admin_view(self.assign_products), name='assign-products'),
            *super().get_urls(),
        ]
        return custom_urls
    
    def assign_products(self, request):
        opts = self.model._meta
        if request.method == 'POST':
            form = ProductAssignForm(request.POST)
            if form.is_valid():
                from_name = form.cleaned_data['from_name']
                to_name = form.cleaned_data['to_name']
                assign_collections_name = form.cleaned_data['assign_collections_name']
                tags = form.cleaned_data['tags']

                print(f"Searching products from '{from_name}' to '{to_name}'")

                # Normalizing the names by removing whitespace and non-alphanumeric characters
                from_name_normalized = ''.join(e for e in from_name if e.isalnum()).lower()
                to_name_normalized = ''.join(e for e in to_name if e.isalnum()).lower()

                # Search by search_name
                products = Product.objects.filter(
                    search_name__gte=from_name_normalized,
                    search_name__lte=to_name_normalized
                )

                print(f"search_handle__gte={from_name_normalized}, search_handle__lte={to_name_normalized}")
                print(f"Found {products.count()} products")

                for product in products:
                    if assign_collections_name:
                        print(f"Assigning collections to product '{product.name}'")
                        product.collections.set(assign_collections_name)
                    if tags:
                        print(f"Assigning tags to product '{product.name}'")
                        product.tags.set(tags)
                    product.save()

                return HttpResponseRedirect(request.path_info)
        else:
            form = ProductAssignForm()

        context = dict(
            self.admin_site.each_context(request),
            title="Assign Products",
            form=form,
            opts=opts,
            **self.admin_site.each_context(request),
        )
        return render(request, 'admin/assign_products.html', context)

assign_products.html:

{% block extrahead %}
    {{ block.super }}
    {{ form.media }}
    <link rel="stylesheet" type="text/css" href="{% static 'css/admin_custom.css' %}">
    <script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
    <script type="text/javascript" src="{% static 'admin/js/core.js' %}"></script>
    <script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
    <script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.init.js' %}"></script>
    <script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.form.js' %}"></script>
{% endblock %}

{% block javascript %}
    {{ block.super }}
    <script type="text/javascript">
        django.jQuery(document).ready(function() {
            django.jQuery('.selectfilter').filterchain();
        });
    </script>
{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
    <a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
    &rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
    &rsaquo; <a href="{% url 'admin:assign-products' %}">{% translate 'Assign Products' %}</a>
</div>
{% endblock %}

{% block content %}
<div class="container">
    <h1>{% translate 'Assign Products' %}</h1>
    <form action="." method="post">
        {% csrf_token %}
        {% for field in form %}
            <div class="form-group">
                <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                {{ field }}
                {% if field.help_text %}
                    <small class="form-text text-muted">{{ field.help_text }}</small>
                {% endif %}
                {% for error in field.errors %}
                    <div class="text-danger">{{ error }}</div>
                {% endfor %}
            </div>
        {% endfor %}
        <button type="submit" class="btn btn-primary">{% translate 'Assign Products' %}</button>
    </form>
</div>
{% endblock %}

Я пробовал кризисные формы и нормальные формы, я пробовал спрашивать ChatGPT и ClaudeAI и даже искать в google.

Помощь будет очень признательна!

У меня была такая же проблема, когда я пытался использовать этот виджет в админке django, я смог решить ее filter_horizontal атрибут на ModelAdmin классе, этот атрибут необходим, если вам нужен специальный javascript виджет django для вашего m2m поля в Django Admin:

Я буду использовать код примера:

## models.py
from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True, null=True)


class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    categories = models.ManyToManyField(Category, related_name='products')

    def __str__(self):
        return self.name

## forms.py
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from .models import Product, Category

class ProductAdminForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = '__all__'

    categories = forms.ModelMultipleChoiceField(
        queryset=Category.objects.all(),
        widget=FilteredSelectMultiple(
            verbose_name="Categories",
            is_stacked=False
        ),
        required=False,
    )

## admin.py
from django.contrib import admin
from .models import Product
from .forms import ProductAdminForm

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm
    list_display = ('name', 'price', 'categories')
    filter_horizontal = ["categories"] ## <- You are missing this

Если у вас есть вопросы, не стесняйтесь их задавать!

Добавьте следующий код в свой класс, в данном случае это ProductAssignForm, в котором я создал свою форму и написал код, отображающий Model.MultipleChoiceField с виджетом FilteredSelectMultiple.

Код таков:

class Media:
        # css = {
        #     'all':['admin/css/widgets.css',
        #            'css/uid-manage-form.css'],
        # }
        # Adding this javascript is crucial
        js = ['/admin/jsi18n/']

Теперь просто обновите страницу, и она должна заработать.

Не стесняйтесь задавать любые вопросы!

Вернуться на верх