Which widget can be used in a form to manage data with many-to-many relationships similar to the widgets of the user’s administrative form? The task seems to be standard, it has a solution, but there is no description of it anywhere. Some kind of “conspiracy of silence” may be …
Welcome @MikeWinny !
It’s not just a widget, there’s also some JavaScript and css involved as well.
You’ll want to find the components associated with the css class related-widget-wrapper
, this is going to include
RelatedObjectLookups.js
indjango.contrib.admin.static.admin.js.admin
,related_widget_wrapper.html
indjango.contrib.admin.templates.admin.widgets
,
andforms.css
,responsive.css
andwidgets.css
indjango.contrib.admin.static.admin.css
.
Note: As these are undocumented, there is no assurance that these are going to remain the same across releases. As with any undocumented feature, it can be changed without notice and without going through the normal deprecation process.
As a result, you might want to copy those components into your project and not just reference the existing versions from the Django installation.
Hi, Ken!
Thank you for answer and recomendations.
For a long time I have been trying to figure out how to use the technologies used in the above code, but so far without success. A very sophisticated, but universal code.
In particular, it is unclear how to set the value of the admin_site
parameter when using the User model from the Django kernel, and which one for models from the application.
Unfortunately, I could not find any examples of successful use of this approach,
First, I think I copied some wrong information in my previous response. (The basic idea was right, but I think I identified the wrong javascript file.)
But beyond that, I think I’m kinda lost here and need more context about what it is you’re trying to do.
If you’re copying these components into your own app, I don’t see where the admin_site
parameter needs to play any part in this.
The basic steps would be to find the JavaScript, CSS, and Templates that the admin uses, copy them to your app, and use them in a manner similar to how the admin does it. But there’s no need to implement everything in your code the way that the admin does it.
(I’ve never done this, so I don’t have anything resembling a sample to work from.)
I believe the key point to keep in mind here is that if it’s done correctly, the Django code in the view doesn’t need to change - this is all front-end work with a custom widget. What the HTML form returns should be the same as what a multi-select box returns, making the use of this widget transparent to Django itself.
Hi, Ken!
I probably defined the order of using the many-to-many functionality from the django admin panel in the application forms.
However, it still refuses to search for a template in the templates folder of the application, and searches for it only in django core folders.
Screen error message contains follow:
Template-loader postmortem
Django tried loading these templates, in this order:
Using engine django:
django.template.loaders.filesystem.Loader: C:\Users\USER1\.virtualenvs\application\lib\site-packages\django\forms\templates\inc\_related_widget_wrapper.html (Source does not exist)
django.template.loaders.app_directories.Loader: C:\Users\USER1\.virtualenvs\application\lib\site-packages\django\contrib\admin\templates\inc\_related_widget_wrapper.html (Source does not exist)
django.template.loaders.app_directories.Loader: C:\Users\USER1\.virtualenvs\application\lib\site-packages\django\contrib\auth\templates\inc\_related_widget_wrapper.html (Source does not exist)
django.template.loaders.app_directories.Loader: C:\Users\USER1\.virtualenvs\application\lib\site-packages\django_bootstrap5\templates\inc\_related_widget_wrapper.html (Source does not exist)
Template ‘_related_widget_wrapper.html’ placed in ‘application/templates/inc’ folder, but in this case django doesn’t even look for it there.
What could be the problem here?
What is your TEMPLATES
setting?
What is your INSTALLED_APPS
setting?
(It doesn’t appear to be looking anywhere in your project, so those are the first two settings I’d check.)
Some standard (and worked without this wdget):
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
INSTALLED_APPS = [
'main',
'acc_req_answers',
'advanced_user',
'subject',
'audited',
'contractor',
'department',
'parameters',
'project',
'currency',
'reports',
'ml_models',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_bootstrap5',
]
I guess this could be tied to the admin_site
parameter in the widget call, which I made equal to admin.site
from django.contrib import admin
, but I have no idea what to equate it to in a regular application.
What does your view and template look like where you’re trying to use this?
I use it in forms. For example, the form for Project atributes edit:
class ProjectForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# personalize choices
self.fields['access_list'].widget = RelatedFieldWidgetWrapper2(
self.fields['access_list'].widget,
self.instance._meta.get_field('access_list').remote_field,
admin.site,
)
access_list = ModelMultipleChoiceField(
queryset=User.objects.all(),
widget=FilteredSelectMultiple(
"Users",
is_stacked=False
)
)
class Meta:
model = AuditProject
fields = ('department', 'audited', 'name', 'manager', 'description', 'access_list')
where RelatedFieldWidgetWrapper2
- copy of admin’s RelatedFieldWidgetWrapper
with changed template_name
, and access_list
- m2m relation with User
object.
With form interact by view:
class ProjectEditView(LoginRequiredMixin, UpdateView):
model = AuditProject
template_name = 'project/project_edit.html'
form_class = ProjectForm
success_url = reverse_lazy('projects_list_url')
I’ve finally had a chance to spend some time to look at this in more detail.
In a way, it’s both easier and more difficult than I thought. There’s a lot of styling that needs to be accounted for in your CSS. If you’ve got a lot of css that you’re using, you may find that you’ll need to override some of it. For example, the admin css overrides the styling of the <h2>
element.
Anyway, looking at the minimal requirements - this is what my PoC looks like.
CSS and JS currently included from the admin:
<link href="{% static 'admin/css/responsive.css' %}" type="text/css" rel="stylesheet">
<link href="{% static 'admin/css/widgets.css' %}" type="text/css" rel="stylesheet">
<link href="{% static 'admin/css/forms.css' %}" type="text/css" rel="stylesheet">
<link href="{% static 'admin/css/base.css' %}" type="text/css" rel="stylesheet">
<script src="{% url 'admin:jsi18n' %}"></script>
<script src="{% static 'admin/js/core.js' %}"></script>
<script src="{% static 'admin/js/SelectBox.js' %}"></script>
<script src="{% static 'admin/js/SelectFilter2.js' %}"></script>
The four JS files all seem to be necessary. Those CSS files aren’t strictly necessary, but you would need to know what selectors in those files are being used by the widget, and style them accordingly.
The template fragment for rendering the form:
{{ form }}
(Yep, the work for converting the select element to this widget is done by the JavaScript.)
The view:
class TestUpdate(UpdateView):
model = TestModel
form_class = TestForm
template_name = 'test/form_template.html'
Strictly speaking, the template_name attribute isn’t necessary if I name the template according to the defined default. (It would be something like test/mytest_detail.html
)
The form:
from django.contrib.admin.widgets import FilteredSelectMultiple
class TestForm(forms.ModelForm):
class Meta:
model = TestModel
fields = '__all__'
widgets = {
'user': FilteredSelectMultiple(
'Users',
is_stacked=False,
)
}
In this sample, my TestModel
has an m2m with User
through a field named user
.
Hi, Ken!
Thank you!
You found an easier way to solve the problem.
For more reliability, I moved the widget code, script and style files inside the project.