Last active
November 22, 2023 11:25
-
-
Save jdklub/9261959 to your computer and use it in GitHub Desktop.
Edit a many-to-many relationship (ManyToManyField) on the Django Admin change list page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class BookChangeList(ChangeList): | |
def __init__(self, request, model, list_display, list_display_links, | |
list_filter, date_hierarchy, search_fields, list_select_related, | |
list_per_page, list_max_show_all, list_editable, model_admin): | |
super(BookChangeList, self).__init__(request, model, list_display, list_display_links, | |
list_filter, date_hierarchy, search_fields, list_select_related, | |
list_per_page, list_max_show_all, list_editable, model_admin) | |
# these need to be defined here, and not in BookAdmin | |
self.list_display = ('name', 'categories') | |
self.list_display_links = ['name'] | |
self.list_editable = ['categories'] | |
class BookForm(forms.ModelForm): | |
# this the bit of custom CSS we want to add | |
style_text = "height:80px; overflow-y:scroll;" | |
# here we only need to define the field we want to be editable | |
categories = forms.ModelMultipleChoiceField(queryset=BookCategory.objects.all(),widget=StyleableCheckboxSelectMultiple(ul_attrs={"style":style_text}),required=False) | |
class BookAdmin(admin.ModelAdmin): | |
def get_changelist(self, request, **kwargs): | |
return BookChangeList | |
def get_changelist_form(self, request, **kwargs): | |
return BookForm | |
admin.site.register(Book, BookAdmin) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# this subclass allows us to add a bit of our own CSS at the <ul> level | |
class StyleableCheckboxSelectMultiple(forms.CheckboxSelectMultiple): | |
def __init__(self, attrs=None, ul_attrs=None): | |
self.ul_attrs = ul_attrs | |
super(ScrollableCheckboxSelectMultiple, self).__init__(attrs) | |
def render(self, name, value, attrs=None, choices=()): | |
html = super(ScrollableCheckboxSelectMultiple, self).render(name, value, attrs, choices) | |
final_attrs = self.build_attrs(self.ul_attrs) | |
return mark_safe(html.replace('<ul>','<ul%s>' % flatatt(final_attrs))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class BookCategory(models.Model): | |
name = models.CharField(max_length=255) | |
class Book(models.Model): | |
name = models.CharField(max_length=255) | |
categories = models.ManyToManyField('BookCategory', blank=True, null=True) | |
def __unicode__(self): | |
return self.name |
When I implemented this I noticed that the checkbox on the left side of the admin were missing, to have them display I had to add action_checkbox to the list_display in the ChangeList class.
Like this:
class BookChangeList(ChangeList):
def __init__(self, request, model, list_display, list_display_links,
list_filter, date_hierarchy, search_fields, list_select_related,
list_per_page, list_max_show_all, list_editable, model_admin):
super(BookChangeList, self).__init__(request, model, list_display, list_display_links,
list_filter, date_hierarchy, search_fields, list_select_related,
list_per_page, list_max_show_all, list_editable, model_admin)
# these need to be defined here, and not in BookAdmin
self.list_display = ('action_checkbox', 'name', 'categories')
self.list_display_links = ['name']
self.list_editable = ['categories']
Made couple pf fixes to support Python3 and Django 1.11. See https://gist.github.com/efimerdlerkravitz/6177b656563e86f57f1fb81c1a18bf85
"""
base https://gist.github.com/jdklub/9261959
https://gist.github.com/quxf2012/b1827d2bf02322c2779e8309ba089163
usage:
@admin.register(XxxModel)
class XxxModelAdmin(ModelMultipleListEdit, admin.ModelAdmin):
m_list_editable = {'mtomfield': MtomModel.objects.all()} # ModelMultipleListEdit use
"""
from django import forms
from django.forms.utils import flatatt
from django.utils.safestring import mark_safe
__all__ = ['ModelMultipleListEdit']
class StyleableCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
def __init__(self, attrs=None, ul_attrs=None):
self.ul_attrs = ul_attrs
super().__init__(attrs)
def render(self, *args, **kwargs):
html = super().render(*args, **kwargs)
final_attrs = self.build_attrs(self.ul_attrs)
return mark_safe(html.replace('<ul>', '<ul%s>' % flatatt(final_attrs)))
class ModelMultipleListEdit:
m_list_editable = None # dict # m_list_editable={ "field": field_queryset } #field_queryset
def get_changelist_form(self, request, **kwargs):
if not self.m_list_editable:
return super().get_changelist_form(request, **kwargs)
# dynamic create Form
cls_dict = {}
for field, queryset in self.m_list_editable.items():
# dynamic create ManyToManyField form field
cls_dict[field] = forms.ModelMultipleChoiceField(queryset=queryset, widget=StyleableCheckboxSelectMultiple(ul_attrs={"style": "height:80px; overflow-y:scroll;"}), required=False)
cls = type('CloudAppForm', (forms.ModelForm,), cls_dict)
return cls
def get_changelist_instance(self, request):
changeList = super().get_changelist_instance(request)
if not self.m_list_editable:
return changeList
# injection ManyToManyField to list_display list_editable ;
# use to skip ManyToManyField check;
list_display = list(self.list_display)
list_editable = list(self.list_editable)
for i in self.m_list_editable: # key
if i not in list_display:
list_display.append(i)
if i not in list_editable:
list_editable.append(i)
self.list_display = list_display
self.list_editable = tuple(list_editable)
# fix first request display
changeList.list_display = list_display
if changeList.is_popup:
changeList.list_editable = ()
else:
changeList.list_editable = list_editable
# fix first request display
return changeList
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Very helpful, thank you!