MultiValueDictKeyError with modified inline template in Django admin - python

In Django 1.11, I have 2 models, Foo and Bar:
class Foo(models.Model):
name = models.CharField()
class Bar(models.Model):
name = models.CharField()
foo = models.ForeignKey(Foo)
My admin.py looks like this:
class BarInline(admin.StackedInline):
model = Bar
template = 'admin/edit_inline/list.html'
class FooAdmin(admin.ModelAdmin):
fields = ('name')
inlines = [BarInline]
I use a customised template to show the Bar inline form, because I don't want the forms, just links to the edit pages for each Bar. list.html looks like this:
{% load i18n admin_urls static %}
<div class="js-inline-admin-formset inline-group" data-inline-type="stacked">
<fieldset class="module {{ inline_admin_formset.classes }}">
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
{{ inline_admin_formset.formset.management_form }}
{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if forloop.last %} empty-form last-related{% endif %}">
<h3 style="overflow:auto"><span style="float:left">{{ inline_admin_formset.opts.verbose_name|capfirst }}: {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span><span style="float:right">{% if inline_admin_form.original %}Change Delete{% endif %}</span>
</h3>
</div>{% endfor %}
<div class="add-row">
Add a Bar
</div>
</fieldset>
</div>
The problem is that when I edit an existing Foo, and click Save, I get the error:
MultiValueDictKeyError at /admin/app/foo/1/change/
"'bar_set-0-id'"
EDIT: Stacktrace

If you don't want the form for the inline, the easiest approach is to not make the field(s) editable. This will render the objects with their values, but not editable in a form. The other way to display this is admin.TabularInline.
The options for inlines can be found here; https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#inlinemodeladmin-options
The option that you will likely want to enable is show_change_link
What you'd end up with is something like;
class BarInline(admin.StackedInline):
model = Bar
fields = ('name', )
readonly_fields = ('name', )
show_change_link = True

For anyone interested, I ended up extending the template. I created a file change_form.html in templates/admin/app/foo/, and overrode the after_related_objects block:
{% extends 'admin/change_form.html' %}
{% load admin_urls %}
{% block after_related_objects %}
<div class="js-inline-admin-formset inline-group">
<fieldset class="module">
{% for bar in original.bar_set.all %}
<h3>
{{ bar.name }}
Change
</h3>
{% endfor %}
</fieldset>
</div>
{% endblock %}

Related

Django AdminInline on many-to-many relationship

I have two models like this:
class ArtCollection(models.Model):
# nothing important in this case
class Artwork(models.Model):
...
name = models.CharField(max_length=200, null=True, blank=True)
art_collections = models.ManyToManyField("ArtCollection", related_name="artworks")
image = models.ImageField(...)
Recently, I've change the relationship between them from FK for ArtCollection on Artwork model to m2m (as seen as above). What I would like to have now is something that I had before, particulary ArtworkInline in admin panel on ArtCollection change view (editable artwork fields like name, image change and so on). But it doesn't work. The only solution I've came across is this one (I know I should make an image preview rather than display its name - but its just an example):
from inline_actions.admin import InlineActionsMixin, InlineActionsModelAdminMixin
class ArtworkInline(admin.StackedInline):
model = ArtCollection.artworks.through
extra = 0
fields = ['artwork_image']
readonly_fields = ['artwork_image']
def artwork_image(self, instance):
return instance.artwork.image
artwork_image.short_description = 'artwork image'
class ArtCollectionAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin):
...
inlines = [ArtworkInline]
Is it possible to have the editable fields in m2m relationship in inline django panel? I also use grappeli and custom template for inline (which are useless after changing the relationship - they have worked pretty well with FK, now I can have only readable_fields on default template).
{% extends 'admin/stacked_inline.html' %}
{% block fieldset %}
<fieldset class="module aligned {{ fieldset.classes }}">
{% for line in fieldset %}
{% with forloop.counter as counter %}
{% for field in line %}
{{ field.errors }}
{% if counter == 2 or counter == 6 %}
{% elif counter <= 7 %}
<p>{{ field.field }}</p>
{% endif %}
{% endfor %}
{% endwith %}
{% endfor %}
</fieldset>
{% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission and inline_admin_form.original %}
<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>
{% endif %}
{% endblock %}
Thanks for any advices.

How To Provide A Class To Fields In A Django Template?

I was working a project on mine, in which I needed to provide a class to a field in form which is a input box, but don't know how to do so.
My Template:
{% extends 'diary/base.html' %}
{% block title %}Register{% endblock title %}
{% block content %}
{% load staticfiles %}
<link rel= "stylesheet" type= "text/css" href = "{% static 'users/register.css' %}">
<div id="login-box">
<div class="left-box">
<h1>Register</h1>
<form action="{% url 'register' %}" method="post">
{% csrf_token %}
{% for non_field_error in form.non_field_errors %}
<p>{{ non_field_error }}</p>
{% endfor %}
{% for field in form %}
{{ field }}
{% for error in field.errors %}
<p>{{ error }}</p>
{% endfor %}
{% endfor %}
<input type="submit" value="SIGN UP" name="signup-button" class="signup-btn">
</form>
</div>
<div class="right-box">
</div>
</div>
{% endblock content %}
In this specific part:
{% for field in form %}
{{ field }}
{% for error in field.errors %}
I want to provide a class to {{ field }}.
I tried this:
{{ field(class="txtb") }}
This is how I use to provide a class in Flask, but in Django this didn't worked.
Any Help Would Be Appreciated!
django-widget-tweaks
I'm using django-widget-tweaks module for set HTML attribute in templates.
installation
install module using pip install django-widget-tweaks
add to INSTALLED_APPS in settings.py
INSTALLED_APPS = [
...
'widget_tweaks',
...
]
{% load widget_tweaks %} in templates file
usage
in templates code:
{% load widget_tweaks %}
...
{% for field in form %}
{% render_field field class="txtb" %}
...
in HTML, looks like this:
<input class="txtb" ... ...>
reference django-widget-tweaks github page for more information.
manually
Detailed description is in here
forms.py:
class YourForm(forms.ModelForm):
modelfield = forms.CharField(
...,
widget=forms.TextInput(
attrs={
'class': 'txtb'
}
}
)
class Meta:
model = ...
fields = ...
And I have a lot of information about django to simpleisbetterthancomplex.com
django-widget-tweaks also learned here

is_paginated not working for django Generic Views

I've been using django built-in pagination (is_paginated) in few of my pages. They are all working fine. Except for the search page where the pagination should only appear based on the filtered queryset.
I've checked through few other thread but it ain't helping much.
How do I use pagination with Django class based generic ListViews?
Django template tag exception
Here's a mini version of what I have so far:-
1)views.py
class SearchBookView(ListView):
template_name = 'books/search_book.html'
paginate_by = '2'
context_object_name = 'book'
form_class = SearchBookForm
def get(self, request):
form = self.form_class(request.GET or None)
if form.is_valid():
filtered_books = self.get_queryset(form)
context = {
'form' : form,
'book' : filtered_books,
}
else:
context = {'form': form}
return render(request, self.template_name, context)
def get_queryset(self, form):
filtered_books = Book.objects.all()
if form.cleaned_data['title'] != "":
filtered_books = filtered_books.filter(
title__icontains=form.cleaned_data['title'])
return filtered_books
def get_context_data(self):
context = super(SearchBookView, self).get_context_data()
return context
2) search_book.html (template)
{% crispy form %}
{% if book %}
<p>Found {{ book|length }} book{{ book|pluralize }}.</p>
{% for book in book %}
<div class="card">
<div style="height:170px; border:solid #111111;" class="col-md-3">
Ima
</div>
<div class="whole-card col-md-9">
<div class="title">"{{ book.title }}"</div>
<div>{{ book.description }}</div>
Read More
</div>
</div>
{% endfor %}
{% else %}
<p>No book matched your searching criteria.</p>
{% endif %}
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
previous
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
next
{% endif %}
</span>
</div>
{% endif %}
forms.py
class SearchBookForm(forms.Form):
title = forms.CharField(max_length=20)
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.add_input(Submit('search', 'Search', css_class='btn'))
self.helper.form_method = 'GET'
self.helper.layout = Layout('title')
super(SearchBookForm, self).__init__(*args, **kwargs)
------------------UPDATE------------------
Though I understand Daniel Roseman's answer but as I am fairly new to django, I am not sure how to implement the whole thing, hitting plenty of "X not accessible, X is not attribute of Y" and etc. After much digging, I found some other useful posts on this same matter.
Django: Search form in Class Based ListView
Updating context data in FormView form_valid method?
Django CBV: Easy access to url parameters in get_context_data()?
Django class based view ListView with form
URL-parameters and logic in Django class-based views (TemplateView)
Another problem I encounter is I am unable to access the parameters in URL using self.kwargs as what suggested in most of the posts. In the final link I posted above, Ngenator mentioned that URL parameters has to be accessed using request.GET.get('parameter'). I used that and it's working fine for me.
By combining everything, here's the revised piece of coding I have. Just in case anyone is having the same problem as me.
1) views.py
class SearchBookView(ListView):
template_name = 'books/search_book.html'
paginate_by = '3'
context_object_name = 'book_found'
form_class = SearchBookForm
model = Book
def get_queryset(self):
object_list = self.model.objects.all()
title = self.request.GET.get('title', None)
if title is not None and title != "":
object_list = object_list.filter(title__icontains=title)
else:
object_list = []
return object_list
def get_context_data(self):
context = super(SearchBookView, self).get_context_data()
form = self.form_class(self.request.GET or None)
context.update({
'form': form,
})
return context
2) search_book.html (template)
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load staticfiles %}
{% load bootstrap_pagination %}
{% block title %}Search Page{% endblock %}
{% block content %}
<div class="container">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
{% crispy form %}
{% if book_found %}
<p>Found {{ paginator.count }} book{{ book_found_no|pluralize }}.</p>
{% for book in book_found %}
<div class="wholecard">
<div style="height:170px; border:solid #111111;" class="col-md-3">
Image
</div>
<div class="card col-md-9">
<div class="card-title">"{{ book.title }}"</div>
<div>{{ book.description }}</div>
Read More
</div>
</div>
{% endfor %}
{% else %}
<p>No book matched your searching criteria.</p>
{% endif %}
{% bootstrap_paginate page_obj %}
</div>
{% endblock %}
And I ended up using jmcclell's bootstrap-pagination also for pagination. Saved me lots of time! Good stuff...
You've specifically overridden the get method so that it defines its own context, and never calls the default methods, so naturally none of the default context bars are available.
Don't do that; you should almost never be overriding the get and post methods. You should probably move all the form stuff directly into get_queryset.
It's working
views.py
class UserListView(ListView):
model = User
template_name = 'user_list.html'
context_object_name = 'users'
paginate_by = 10
def get_queryset(self):
return User.objects.all()
templates/user_list.html
{% if is_paginated %}
<nav aria-label="Page navigation conatiner">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li>« PREV </li>
{% else %}
<li class="disabled page-item"><a class="page-link">PREV !</a></li>
{% endif %}
{% for i in %}
{{ i }}
{% endfor %}
{% if page_obj.has_next %}
<li> NEXT »</li>
{% else %}
<li class="disabled page-item"><a class="page-link">NEXT !</a></li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}

Django admin display multiple fields on the same line

I have created a model, it will automatically display all the fields from the model and display it on the admin page.
Now, I have a problem, I would like to have two fields on the same line, to do this I have to specify the fieldsets at ModelAdmin:
fieldsets = (
(None, {
'fields': (('firstname', 'lastname'),)
}),
)
Do I have to specify all the fields? Because there are many fields in the database I need to specify.
Wrap those fields on their own tuple.
class TestAdmin(admin.ModelAdmin):
fields = (
'field1',
('field2', 'field3'),
'field4'
)
In the above example, fields field2 and field3 are shown on one line.
I'm afraid there's not an easy way to do it.
One option is to override the change_form.html template for that ModelAdmin and style the form as you like.
Another alternative is to do custom ModelForm and define a field with a widget that renders two input fields, in the form's .save() method, set the widget resulting value (a tuple) to both fields.
There is an article may be useful
http://amk1.wordpress.com/2010/09/23/a-2-column-django-admin-form/
Article is quote below:
Django is great. The bundled admin interface makes it better. But as the number of items on the form gets bigger, the amount of wasted space increases because the layout is single column. Coupled with left alignment on wide-screen monitors, my users usually end their day with a condition we call “eyeballs misalignment”.
So I improvised and changed the form (and StackedInline) to a 2-up layout. No more “eyeballs misalignment”.
The corresponding template for Django 1.2.1 (/contrib/admin/templates/admin/includes/fieldset.html) looks like this, modified lines highlighted:
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
<table border=0 width=100%>
{% for line in fieldset %}
{% cycle '<tr>' '' %}
<td width=50%>
<div style="border-bottom:0" class="form-row{% if line.errors %} errors{% endif %}{% for field in line %} {{ field.field.name }}{% endfor %}">
{{ line.errors }}
{% for field in line %}
<div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}>
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field }}
{% endif %}
{% endif %}
{% if field.field.field.help_text %}
<p class="help">{{ field.field.field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
</div>
</td>
{% cycle '' '</tr>' %}
{% endfor %}
</table>
</fieldset>
this has worked for me
fieldsets=(
("My Group",{"fields": (tuple(['field1','field1']),),}),
)
It's stupid, but yes, if you're going to use the fieldsets tuple-within-a-tuple method, you have to then specify all the fields that should show on your form.
Agreed, that its annoying, but its tuple of tuples from list of fields.
you can use list comprehension and change list to tuple.
Here is an example for skipping some fields, that you want to give some special attention WHILE including rest normal way.
skipped=[]
alist = [field.name for field in <model_name>._meta.fields if field.name not in skipped]
fieldsets = tuple(alist)
*** play with skipped ***
with small tweaking this should work.

How do I hide the field label for a HiddenInput widget in Django Admin?

I've got a bit of Django form code that looks like this:
class GalleryAdminForm(forms.ModelForm):
auto_id=False
order = forms.CharField(widget=forms.HiddenInput())
And that makes the form field go away, but it leaves the label "Order" in the Django admin page. If I use:
order = forms.CharField(widget=forms.HiddenInput(), label='')
I'm still left with the ":" between where the field and label used to be.
How do I hide the whole thing?!
Oraculum has got it right. You shouldn't be cleaning this up on the client side. If it is clutter, then you shouldn't be sending it to client at all. Building on Oraculum's answer, you should use a custom form template because you you probably still want the hidden values in the form.
{% for field in form.visible_fields %}
<div>
{{ field.errors }}
<span class="filter-label">{{ field.label_tag }}</span><br>
{{ field }}
</div>
{% endfor %}
{% for field in form.hidden_fields %}
<div style="display:none;">{{ field }}</div>
{% endfor %}
Using a custom form template to control hidden fields is cleaner because it doesn't send extraneous info to the client.
I can't believe several people have suggested using jQuery for this...
Is it a case of: when the only tool you know is a hammer everything looks like a nail?
Come on, if you're going to do it from the client-side (instead of fixing the source of the problem in the back-end code) surely the right place to do it would be in CSS?
If you're in the admin site then it's a bit harder but if it's a regular page then it's easy to just omit the whole label from the form template, for example
If you're in the admin site then you could still override the as_table, as_ul, as_p methods of BaseForm (see django/forms/forms.py) in your GalleryAdminForm class to omit the label of any field where the label is blank (or == ':' as the value may be at this stage of rendering)
(...looking at lines 160-170 of forms.py it seems like Django 1.2 should properly omit the ':' if the label is blank so I guess you're on an older version?)
Try
{% for field in form.visible_fields %}
I think it's simpler to achieve the ":" label omission for HiddenInput widget by modifying class AdminField(object) in contrib/admin/helpers.py from :
if self.is_checkbox:
classes.append(u'vCheckboxLabel')
contents = force_unicode(escape(self.field.label))
else:
contents = force_unicode(escape(self.field.label)) + u':'
to :
if self.is_checkbox:
classes.append(u'vCheckboxLabel')
contents = force_unicode(escape(self.field.label))
else:
contents = force_unicode(escape(self.field.label))
#MODIFIED 26/10/2009
if self.field.label <> '':
contents += u':'
# END MODIFY
Check the answer at Create a hidden field in the admin site, it can be done without JavaScript by overriding admin/includes/fieldset.html From there, you can inject a CSS class, and do the rest.
In theory, you should be able to pass label_suffix into the form constructor. However, the Django admin ignores this.
You've been bitten by two bugs in Django: #18134 'BoundField.label_tag should include form.label_suffix' (fixed in trunk, should be in 1.6) and to a lesser extent #11277 Hidden fields in Inlines are displayed as empty rows.
Currently, the best solution is to override the admin fieldset template. Use a HiddenInput for your widget, then override the admin fieldset template (documented here). Just create a templates/admin/includes/fieldset.html with the following contents:
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
{% for line in fieldset %}
<div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
{% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %}
{% for field in line %}
<div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}"{% endif %}>
{% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{# only show the label for visible fields #}
{% if not field.field.is_hidden %}
{{ field.label_tag }}
{% endif %}
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field }}
{% endif %}
{% endif %}
{% if field.field.help_text %}
<p class="help">{{ field.field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
</fieldset>
Based upon the solution by Wilfried Hughes I 've changed the fieldset.html with little improvements.
The code snippet below not only hides the input element instead if the fieldset contains only one single element which input-type is set to hidden it also hides the surrounding div-elements wasting no space in the form.
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
{% for line in fieldset %}
<div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}"{% if line.fields|length_is:'1' %}{% for field in line %}{% if field.field.is_hidden %} style="display: none"{% endif %}{% endfor %}{% endif %}>
{% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %}
{% for field in line %}
<div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}"{% endif %}{% if field.field.is_hidden %} style="display: none"{% endif %}>
{% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{# only show the label for visible fields #}
{% if not field.field.is_hidden %}
{{ field.label_tag }}
{% endif %}
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field }}
{% endif %}
{% endif %}
{% if field.field.help_text %}
<p class="help">{{ field.field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
The following removes the ':' from all your form fields. I've only tried it with the forms.Form class, but I believe it should work for forms.ModelForm too.
In Django forms, the ':' after the labels is the label_suffix. You can change or remove the label_suffix by creating a subclass of ModelForm, here called UnstyledForm, and redefining the initialization function with label_suffix set to an empty string. Then use your new UnstyledForm class.
class UnstyledForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
kwargs.setdefault('label_suffix', '')
super(UnstyledForm, self).__init__(*args, **kwargs)
class GalleryAdminForm(UnstyledForm):
auto_id=False
order = forms.CharField(widget=forms.HiddenInput())
I hope that helps!
Another way to do it, but i think it still better to iterate form.visible_fields & form.hidden_fields
<form action="{% url 'some_url' param %}" method="POST">
{% csrf_token %}
<div class="row">
{% for field in form %}
{% if not field.is_hidden %}
<div class="col-md-6">
{{ field.label_tag }}
{{ field.error }}
{{ field }}
</div>
{% else %}
{{ field }}
{% endif %}
{% endfor %}
</div>
</form>
If you're using JQuery this should do the trick:
Your form
TO_HIDE_ATTRS = {'class': 'hidden'}
class GalleryAdminForm(forms.ModelForm):
auto_id=False
order = forms.CharField(widget=forms.TextInput(attrs=TO_HIDE_ATTRS))
Javascript code to add to your template
$(document).ready(function(){
$('tr:has(.hidden)').hide();
});
That works if you're rendering your form as a table. If you want to make it work with any kind of form rendering you can do as follows:
$(document).ready(function(){
$('{{ form_field_container }}:has(.hidden)').hide();
});
And add form_field_container to your template context. An example:
If you render your form like this:
<form>
<span>{{ field.label_tag }} {{ field }}</span>
</form>
Your context must include:
'form_field_container': 'span'
You get the idea...

Categories

Resources