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>
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
› <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/']
Теперь просто обновите страницу, и она должна заработать.
Не стесняйтесь задавать любые вопросы!