I am trying to show the forms.ModelMultipleChoiceField on custom new admin form page but it doesn't seem to show it the way it is showing on the Django page that was already made e.g. model product django admin page. Mine forms.ModelMultipleChoiceField is looking like this: Shows how my forms.ModelMultipleChoiceField looks like When it should look like this: Shows how forms.ModelMultipleChoiceField should look like
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'))
partial code form 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 %}
I have tried crispy forms and normal forms, I have tried asking ChatGPT and ClaudeAI and even searching google.
Help would be really appreciated!
My models.py code:
class Collection(models.Model):
"""
TODO: Modify product method to get all the products in the collection and the subcollections.
TODO: THe functionality of default .all() is renamed to .all_self()
"""
parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='subcollections') # Store the parent collection, linked to itself
cid = ShortUUIDField(unique=True, length=10, max_length=15, prefix="col", alphabet="abcdefghijklmnopqrstuvwxyz0123456789", editable=False)
name = models.CharField(max_length=255)
description = models.TextField(default='No description provided.', blank=True, null=True)
images = models.ManyToManyField('Image', related_name='collections', blank=True)
has_image = models.BooleanField(default=False)
products = models.ManyToManyField('Product', related_name='collections', blank=True)
tags = models.ManyToManyField('Tag', related_name='collections', blank=True)
product_count = models.IntegerField(default=0) # Store the number of products in this collection only
total_product_count = models.IntegerField(default=0) # Store the number of products in this collection and all subcollections
created_at = models.DateTimeField(auto_now_add=True) # Stores when the collection field was created
handle = models.CharField(max_length=255, null=True, blank=True) # This will be used to generate the URL
active = models.BooleanField(default=True) # This will be used to determine if the collection is active or not
class Product(models.Model):
pid = ShortUUIDField(unique=True, length=10, max_length=20, prefix="prd", alphabet="abcdefghijklmnopqrstuvwxyz0123456789", editable=False)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
name = models.CharField(max_length=255, default="Fresh Pear")
images = models.ManyToManyField('Image', related_name='products', blank=True)
description = models.TextField(null=True, blank=True, default="This is the description")
price = models.DecimalField(max_digits=9999999999, decimal_places=2, default="0.00")
old_price = models.DecimalField(max_digits=9999999999, decimal_places=2, default="0.00", blank=True)
specifications = models.JSONField(null=True, blank=True, default=dict)
product_status = models.CharField(choices=PRODUCT_STATUS, max_length=10, default="in_review")
status = models.BooleanField(default=True)
# in_stock = models.BooleanField(default=True)
quantity = models.DecimalField(max_digits=9999999999, decimal_places=0, default=0, blank=True)
featured = models.BooleanField(default=False)
digital = models.BooleanField(default=False)
sku = ShortUUIDField(unique=True, length=8, max_length=11, prefix="sku", alphabet="0123456789")
date = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(null=True, blank=True)
handle = models.CharField(unique=True, max_length=255, null=True, blank=True) # This will be used to generate the URL
search_name = models.CharField(max_length=255, null=True, blank=True) # This will be used to search the product
tags = models.ManyToManyField('Tag', related_name='products', blank=True)
# product.collections.[method] refers to Collection MTM Field declared in collection model
# tags = models.ForeignKey(Tag, on_delete=models.SET_NULL, null=True)
class Meta:
verbose_name_plural = "Products"
def __str__(self):
return self.name
def get_percentage(self):
# (1000 - 900) / 1000 * 100 = 10% discount
# Handle the cases of zero
if self.old_price == 0:
return 0
return (self.old_price - self.price) / self.old_price * 100
def save(self, *args, **kwargs):
self.updated = timezone.now()
super().save(*args, **kwargs)
Add the following code to your class, in this case, it is ProductAssignForm which is where I created my form and wrote the code that displayed Model.MultipleChoiceField with widget as FilteredSelectMultiple.
The code is:
class Media:
# css = {
# 'all':['admin/css/widgets.css',
# 'css/uid-manage-form.css'],
# }
# Adding this javascript is crucial
js = ['/admin/jsi18n/']
Now, just refresh the page and it should work.
Feel free to ask any questions you may like!