How can I display a Django admin inline model within a fieldset? - django

Consider the following ModelAdmin. In this instance, I'd like to have the inline "Book" UI display between the "None" fieldset and the Notes fieldset. Is that possible?
class AuthorAdmin(admin.ModelAdmin):
inlines = [BookInline]
fieldsets = (
(None, {
'fields': ('author_name', 'date_of_birth')
}),
('Notes', {
'fields': (['notes'])
}),
)

Bertrand Bortage posted another solution here: https://groups.google.com/forum/#!topic/django-users/yUq2Nvx_4eM
A late reply to say that I just pushed a fairly clean solution to this
problem in one of my projects:
https://github.com/dezede/dezede/commit/ed13ccaf34494e71fd913fd785c229052f6acdc8.
The idea is to define fieldsets_and_inlines_order in your
ModelAdmin(s), an iterable of 'f' and 'i' characters (for "fieldset"
and "inline") that specifies the order between the first fieldsets and
inlines. If len(fieldsets_and_inlines_order) < len(fieldsets) +
len(inlines), the remaining follows the original behaviour (fieldsets
first, then all inlines).
Example: you have 5 fieldsets and 3 inlines, defining
fieldsets_and_inlines_order = ('f', 'f', 'i', 'f', 'i') will give you:
fieldset fieldset inline fieldset inline fieldset fieldset inline Hope
it helps, Bertrand
I had another idea which is worth considering. Create a readonly placeholder field in your fieldsets for each inline then use jQuery to move the inlines into place for each placeholder. Something like this (jQuery omitted as I haven't written it yet):
fieldsets = (
(None, {
'fields': (
('inline_images',)
('thumbnail_image',),
('inline_authors',)
('title', 'is_active', 'order',)
),
}),
)
readonly_fields = ('inline_images', 'inline_authors')
inline_images = '<span class="replaceme inline_images"></span>'
inline_images.allow_tags = True
inline_authors = '<span class="replaceme inline_authors"></span>'
inline_authors.allow_tags = True
One more thing - there is an open Django issue asking for this positioning of inlines: https://code.djangoproject.com/ticket/4848

Unfortunately this is not possible with (the standard template from) django. If you look at the template for the change_form, you can see that inlines are always rendered separately after the fieldset:
https://github.com/django/django/blob/master/django/contrib/admin/templates/admin/change_form.html
The only work-around I see is to write a custom template with respect to the order you want.

I have constructed another quite generic solution...
In your admin.py add a new field to your Inline:
class YourModelInline(admin.TabularInline):
model = YourModel
after_field = "fieldname_of_field_before_inline"
Then customize render_change_form of AdminClass of the model that holds the Inline:
class EditModelAdmin(model.ModelAdmin):
inlines = [YourModelInline,]
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
sorted_inline_formsets = {}
inline_admin_formsets = context['inline_admin_formsets']
formsets_to_remove = []
for inline_formset in inline_admin_formsets:
if hasattr(inline_formset.opts, 'after_field'):
fieldname = inline_formset.opts.after_field
if fieldname in sorted_inline_formsets:
sorted_inline_formsets[fieldname].append(inline_formset)
else:
sorted_inline_formsets.update({
fieldname: [inline_formset,]
})
formsets_to_remove.append(inline_formset)
for inline_formset in formsets_to_remove:
inline_admin_formsets.remove(inline_formset)
context.update({
'sorted_inline_formsets': sorted_inline_formsets,
'inline_admin_formsets': inline_admin_formsets
})
return super(EditModelAdmin, self).render_change_form(request, context, add=add,
change=change, obj=obj, form_url=form_url)
We are moving all Inlines with extra field into own dictionary with fieldname as key...
For it to be rendered correctly create file /templates/admin/includes/fieldset.html that overrides standard django fieldset.html with following content:
{% load custom_filter %}
<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 %}{% if not line.has_visible_field %} hidden{% 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 %}{% if field.field.is_hidden %} hidden{% endif %}"{% elif field.is_checkbox %} class="checkbox-row"{% 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 %}
{{ field.label_tag }}
{% if field.is_readonly %}
<div class="readonly">{{ field.contents }}</div>
{% else %}
{{ field.field }}
{% endif %}
{% endif %}
{% if field.field.help_text %}
<div class="help">{{ field.field.help_text|safe }}</div>
{% endif %}
</div>
{% if field.field.name %}
{% with field.field.name as fieldname %}
{% if sorted_inline_formsets|get_dict_value:fieldname != False %}
{% for inline_admin_formset in sorted_inline_formsets|get_dict_value:fieldname %}
{% include inline_admin_formset.opts.template %}
{% endfor %}
{% endif %}
{% endwith %}
{% endif %}
{% endfor %}
</div>
{% endfor %}
</fieldset>
This will add sorted inlines after the corresponding field... Now you only need the custom_filter for working with the dictionary in django template, create templatetags/custom_filter.py and add:
#register.filter
def get_dict_value(dict, key):
if key in dict:
return dict[key]
else:
return False
And voila: You can enter any fieldname into any Inline to add it after that field... It is a bit work to setup but if you have several inlines to sort it might be a cleaner way...

Related

Django admin template - grid instead of list

What is the easiest / fastest way to override Django admin form to display grid of elements instead of list? My model includes a field for a 100x100px pic, and it would be easier to see them as a grid of pictures, not a list.
This is not terribly difficult, but it requires you to copy several large chunks of code from the Django library and incorporate them into your own application. Here's the overview:
You'll need to copy and then modify several template tags from django.contrib.admin.templatetags.admin_list:
django.contrib.admin.templatetags.admin_list.items_for_result
django.contrib.admin.templatetags.admin_list.result_list
django.contrib.admin.templatetags.admin_list.results
django.contrib.admin.templatetags.admin_list.result_list_tag
You'll need to copy and then modify a couple of templates:
django/contrib/admin/templates/admin/change_list.html
django/contrib/admin/templates/admin/change_list_results.html
For purposes of illustrating the solution, I have created an app called spam and a model called SpamPhoto, along with a model admin that is customized to display the image tag. I am using Bootstrap 4 for the grid. There are still some issues you might need to track down and figure out, like getting rid of the checkbox that shows up for each item. But this code should get about 90% of the way there.
Here's an overview of the files that I created/modified, and which you'll find below:
spam/models.py: contains the SpamPhoto model
spam/admin.py: contains the SpamPhotoAdmin
spam/templatetags/spamphoto_admin_list.py: contains the template tag / functions that will render the customized change list
spam/templates/admin/spam/spamphoto/change_list.html: this is the main changelist template, and only has a few small changes: it loads our custom template tags, it includes Bootstrap CSS, and it calls a customized template tag (defined below)
spam/templates/admin/spam/spamphoto/change_list_results.html: heavy modifications here, getting rid of the table elements and replacing with divs.
So again, this is a LOT of code, and most of it is lifted directly without very many changes. Here we go:
# spam/models.py
from django.db import models
class SpamPhoto(models.Model):
name = models.CharField(max_length=100)
image = models.ImageField()
# spam/admin.py
from django.contrib import admin
from django.utils.html import mark_safe
class SpamPhotoAdmin(admin.ModelAdmin):
list_display = ('name', 'image_tag')
list_display_links = ('name', 'image_tag')
def image_tag(self, obj):
return mark_safe(f'<img class="img-fluid" src="{obj.image.url}" />')
# spam/templatetags/spamphoto_admin_list.py
from django import template
# Note: you should import these from their "correct" locations! This is cheap/dirty:
from django.contrib.admin.templatetags.admin_list import (ResultList, result_headers, result_hidden_fields,
_coerce_field_name, lookup_field, ObjectDoesNotExist,
display_for_field, datetime, display_for_value, models,
mark_safe, NoReverseMatch, add_preserved_filters, format_html)
from django.contrib.admin.templatetags.base import InclusionAdminNode
register = template.Library()
def spamphoto_items_for_result(cl, result, form):
"""
Cloned from django.contrib.admin.templatetags.admin_list.items_for_result
The only modification is found at the very end of this function, where the HTML
tags are changed from `td` to `div`
"""
def link_in_col(is_first, field_name, cl):
if cl.list_display_links is None:
return False
if is_first and not cl.list_display_links:
return True
return field_name in cl.list_display_links
first = True
pk = cl.lookup_opts.pk.attname
for field_index, field_name in enumerate(cl.list_display):
empty_value_display = cl.model_admin.get_empty_value_display()
row_classes = ['field-%s' % _coerce_field_name(field_name, field_index)]
try:
f, attr, value = lookup_field(field_name, result, cl.model_admin)
except ObjectDoesNotExist:
result_repr = empty_value_display
else:
empty_value_display = getattr(attr, 'empty_value_display', empty_value_display)
if f is None or f.auto_created:
if field_name == 'action_checkbox':
row_classes = ['action-checkbox']
boolean = getattr(attr, 'boolean', False)
result_repr = display_for_value(value, empty_value_display, boolean)
if isinstance(value, (datetime.date, datetime.time)):
row_classes.append('nowrap')
else:
if isinstance(f.remote_field, models.ManyToOneRel):
field_val = getattr(result, f.name)
if field_val is None:
result_repr = empty_value_display
else:
result_repr = field_val
else:
result_repr = display_for_field(value, f, empty_value_display)
if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)):
row_classes.append('nowrap')
row_class = mark_safe(' class="%s"' % ' '.join(row_classes))
# If list_display_links not defined, add the link tag to the first field
if link_in_col(first, field_name, cl):
table_tag = 'th' if first else 'td'
first = False
# Display link to the result's change_view if the url exists, else
# display just the result's representation.
try:
url = cl.url_for_result(result)
except NoReverseMatch:
link_or_text = result_repr
else:
url = add_preserved_filters({'preserved_filters': cl.preserved_filters, 'opts': cl.opts}, url)
# Convert the pk to something that can be used in Javascript.
# Problem cases are non-ASCII strings.
if cl.to_field:
attr = str(cl.to_field)
else:
attr = pk
value = result.serializable_value(attr)
link_or_text = format_html(
'<a href="{}"{}>{}</a>',
url,
format_html(
' data-popup-opener="{}"', value
) if cl.is_popup else '',
result_repr)
yield format_html('<{}{}>{}</{}>', table_tag, row_class, link_or_text, table_tag)
else:
# By default the fields come from ModelAdmin.list_editable, but if we pull
# the fields out of the form instead of list_editable custom admins
# can provide fields on a per request basis
if (form and field_name in form.fields and not (
field_name == cl.model._meta.pk.name and
form[cl.model._meta.pk.name].is_hidden)):
bf = form[field_name]
result_repr = mark_safe(str(bf.errors) + str(bf))
# THIS LINE HAS BEEN CHANGED:
yield format_html('<div{}>{}</div>', row_class, result_repr)
if form and not form[cl.model._meta.pk.name].is_hidden:
# THIS LINE HAS BEEN CHANGED:
yield format_html('<div>{}</div>', form[cl.model._meta.pk.name])
def spamphoto_result_list(cl):
"""
Cloned from django.contrib.admin.templatetags.admin_list.result_list
The only change is to the `results` value in the returned dict, where we call `spamphoto_results`
"""
headers = list(result_headers(cl))
num_sorted_fields = 0
for h in headers:
if h['sortable'] and h['sorted']:
num_sorted_fields += 1
return {
'cl': cl,
'result_hidden_fields': list(result_hidden_fields(cl)),
'result_headers': headers,
'num_sorted_fields': num_sorted_fields,
# THIS LINE HAS BEEN CHANGED:
'results': list(spamphoto_results(cl)),
}
def spamphoto_results(cl):
"""
Cloned from django.contrib.admin.templatetags.admin_list.results
The only changes are where we call `spamphoto_items_for_result` instead of `items_for_result`
"""
if cl.formset:
for res, form in zip(cl.result_list, cl.formset.forms):
# THIS LINE HAS BEEN CHANGED:
yield ResultList(form, spamphoto_items_for_result(cl, res, form))
else:
for res in cl.result_list:
# THIS LINE HAS BEEN CHANGED:
yield ResultList(None, spamphoto_items_for_result(cl, res, None))
#register.tag(name='spamphoto_result_list')
def spamphoto_result_list_tag(parser, token):
"""
Cloned from django.contrib.admin.templatetags.admin_list.result_list_tag
The only change is to the `func` param, which now uses out custom `spamphoto_result_list` function
"""
return InclusionAdminNode(
parser, token,
# THIS LINE HAS BEEN CHANGED:
func=spamphoto_result_list,
template_name='change_list_results.html',
takes_context=False,
)
# spam/templates/admin/spam/spamphoto/change_list.html
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_list %}
<!-- ADDED THIS LINE / INCLUDING OUR CUSTOM TEMPLATE TAGS -->
{% load spamphoto_admin_list %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static "admin/css/changelists.css" %}">
{% if cl.formset %}
<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">
{% endif %}
{% if cl.formset or action_form %}
<script src="{% url 'admin:jsi18n' %}"></script>
{% endif %}
{{ media.css }}
{% if not actions_on_top and not actions_on_bottom %}
<style>
#changelist table thead th:first-child {width: inherit}
</style>
{% endif %}
<!-- ADDED THIS LINE / INCLUDING BOOTSTRAP -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.6.2/dist/css/bootstrap.min.css">
{% endblock %}
{% block extrahead %}
{{ block.super }}
{{ media.js }}
{% endblock %}
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
{% translate 'Home' %}
› {{ cl.opts.app_config.verbose_name }}
› {{ cl.opts.verbose_name_plural|capfirst }}
</div>
{% endblock %}
{% endif %}
{% block coltype %}{% endblock %}
{% block content %}
<div id="content-main">
{% block object-tools %}
<ul class="object-tools">
{% block object-tools-items %}
{% change_list_object_tools %}
{% endblock %}
</ul>
{% endblock %}
{% if cl.formset and cl.formset.errors %}
<p class="errornote">
{% if cl.formset.total_error_count == 1 %}{% translate "Please correct the error below." %}{% else %}{% translate "Please correct the errors below." %}{% endif %}
</p>
{{ cl.formset.non_form_errors }}
{% endif %}
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
<div class="changelist-form-container">
{% block search %}{% search_form cl %}{% endblock %}
{% block date_hierarchy %}{% if cl.date_hierarchy %}{% date_hierarchy cl %}{% endif %}{% endblock %}
<form id="changelist-form" method="post"{% if cl.formset and cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %} novalidate>{% csrf_token %}
{% if cl.formset %}
<div>{{ cl.formset.management_form }}</div>
{% endif %}
{% block result_list %}
{% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %}
<!-- THIS LINE HAS BEEN CHANGED: -->
{% spamphoto_result_list cl %}
{% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %}
{% endblock %}
{% block pagination %}{% pagination cl %}{% endblock %}
</form>
</div>
{% block filters %}
{% if cl.has_filters %}
<div id="changelist-filter">
<h2>{% translate 'Filter' %}</h2>
{% if cl.has_active_filters %}<h3 id="changelist-filter-clear">
✖ {% translate "Clear all filters" %}
</h3>{% endif %}
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
</div>
{% endif %}
{% endblock %}
</div>
</div>
{% endblock %}
# spam/templates/admin/spam/spamphoto/change_list_results.html
{% load i18n static %}
{% if result_hidden_fields %}
<div class="hiddenfields">{# DIV for HTML validation #}
{% for item in result_hidden_fields %}{{ item }}{% endfor %}
</div>
{% endif %}
{% if results %}
<div class="container">
<div class="row">
{% for result in results %}
{% if result.form and result.form.non_field_errors %}
<div>{{ result.form.non_field_errors }}</div>
{% endif %}
<div class="col-sm-2">{% for item in result %}{{ item }}{% endfor %}</div>
{% endfor %}
</div>
</div>
{% endif %}

Why this django formset has stopped saving the content all of a sudden?

I had this view that rendered a form and a formset in the same template:
class LearnerUpdateView(LearnerProfileMixin, UpdateView):
model = User
form_class = UserForm
formset_class = LearnerFormSet
template_name = "formset_edit_learner.html"
success_url = reverse_lazy('pages:home')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
learner = User.objects.get(learner=self.request.user.learner)
formset = LearnerFormSet(instance=learner)
context["learner_formset"] = formset
return context
def get_object(self, queryset=None):
user = self.request.user
return user
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
user = User.objects.get(learner=self.get_object().learner)
formsets = LearnerFormSet(self.request.POST, request.FILES, instance=user)
if form.is_valid():
for fs in formsets:
if fs.is_valid():
# Messages test start
messages.success(request, "Profile updated successfully!")
# Messages test end
fs.save()
else:
messages.error(request, "It didn't save!")
return self.form_valid(form)
return self.form_invalid(form)
Then i wanted to make it prettier and i added the select2 multicheckbox widget and the django-bootstrap-datepicker-plus
Nothing has changed elsewhere, yet when i submit the post it only saves the data relative to User and not to Learner (which relies on the formset)
According to the messages, the formset data is not validated, I don't understand why since i didn't touch the substance at all but just the appearance.
Being a beginner im probably missing something big, I thank in advance whoever can help me find out the problem.
Here below the forms and the template:
(users.forms)
class LearnerForm(forms.ModelForm):
class Meta:
model = Learner
fields = ['locations', 'organization', 'supervisor', 'intro']
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'birthday', 'email', 'profile_pic']
widgets = {
'birthday': DatePickerInput(format='%Y-%m-%d'), }
LearnerFormSet = inlineformset_factory(User, Learner, form=LearnerForm)
template
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
{{ learner_formset.management_form}}
{% for form in learner_formset %}
{% if forloop.first %}
{% for field in form.visible_fields %}
{% if field.name != 'DELETE' %}
<label for="id_{{ field.name }}">{{ field.label|capfirst }}</label>
<div id='id_{{ field.name }}' class="form-group">
{{ field.errors.as_ul }}
{{ field }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
<input class="btn btn-success" type="submit" value="Update"/>
</form>
{% endblock content %}
I figured it out by combing the previous commits. The problem was in the template, the formset wasn't getting validated because i had ignored the hidden fields, which i hear are a common problem when dealing with formsets.
So the correct code is as such:
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
{{ learner_formset.management_form}}
{% for form in learner_formset %}
{% if forloop.first %}
{% comment %} This makes it so that it doesnt show the annoying DELETE checkbox {% endcomment %}
{% for field in form.visible_fields %}
{% if field.name != 'DELETE' %}
<label for="{{ field.name }}">{{ field.label|capfirst }}</label>
<div id="{{ field.name }}" class="form-group">
{{ field }}
{{ field.errors.as_ul }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% for field in form.visible_fields %}
{% if field.name == 'DELETE' %}
{{ field.as_hidden }}
{% else %}
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
<input class="btn btn-success" type="submit" value="Update"/>
</form>
{% endblock content %}

Overriding django admin pagination along with url parameters

I would like to implement custom pagination for my admin panel.
My url looks like the following:
http://localhost:8000/admin/items/?group_id=20
On this URL I do some work to filter the results using the parameter group_id (by overriding get_changelist method).
The page results are corrects, the problem is my pagination ending up like this http://localhost:8000/admin/items/?p=1 whereas I would like the URL to be http://localhost:8000/admin/items/?group_id=20&p=1 and keep the parameter.
Basically I want the same result as How to paginate Django with other get variables? but using Django admin.
How can I keep the parameter along with the pagination?
I've tried overriding pagination.html file but without any success.
Thank you.
Edit
I've tried overriding pagination.html but request.GET.items is still empty (even if my settings file is well configured)
{% load admin_list %}
{% load i18n %}
{% load content_extras %}
<p class="paginator">
{% if pagination_required %}
{% for i in page_range %}
{{ i }}
{% endfor %}
{% endif %}
{{ cl.result_count }} {% if cl.result_count == 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endif %}
{% if show_all_url %}{% trans 'Show all' %}{% endif %}
{% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% trans 'Save' %}">{% endif %}
</p>
FOUND A SOLUTION:
1/ Override changelist_view on admin.py and pass the extra data
def changelist_view(self, request, extra_context=""):
response = super(ItemAdmin, self).changelist_view(request, extra_context)
group_id = request.GET.get('group_id', None)
if group_id:
extra_context = {
'group_id': group_id,
}
response.context_data.update(extra_context)
return TemplateResponse(request, "admin/changelist.html", response.context_data)
2/ Create changelist.html file based on django admin template (copy/paste)
Add {% load content_extras %} at the top of the file (line 3)
Change line {% block pagination %}{% pagination cl %}{% endblock %} with {% block pagination %}{% custom_pagination cl %}{% endblock %}
3/ Create content_extras.py under templatetags folder and write custom_pagination function
from django import template
from django.contrib.admin.templatetags import admin_list
register = template.Library()
#register.inclusion_tag('admin/pagination.html', takes_context=True)
def custom_pagination(context, cl):
pagination = admin_list.pagination(cl)
if 'group_id' in context:
params = (('group_id', context['group_id']),)
pagination['params'] = params
return pagination
4/ Create pagination.html (same location as changelist.html)
{% load admin_list %}
{% load i18n %}
{% load content_extras %}
<p class="paginator">
{% if pagination_required %}
{% for i in page_range %}
{{ i }}
{% endfor %}
{% endif %}
{{ cl.result_count }} {% if cl.result_count == 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endif %}
{% if show_all_url %}{% trans 'Show all' %}{% endif %}
{% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% trans 'Save' %}">{% endif %}
</p>
If you activated django.template.context_processors.request in your settings, you can access parameters from your request directly from your template.
And then you can access the parameters in your templates directly. Something like:
href="?page={{ data.next_page_number }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}"
Research (context_processors.request was moved from django.core to django.templates some time ago)

How to insert extra content in form?

I have form:
class ItemForm(forms.ModelForm):
id = forms.ModelChoiceField(queryset=Item.objects.all(), widget=forms.HiddenInput())
temp = forms.BooleanField(widget=forms.HiddenInput(), required=False)
date = forms.SplitDateTimeField(widget=forms.SplitDateTimeWidget())
... etc
and in template i have:
{% for field in itemForm %}
{% if field.is_hidden %}
{{ field }}
{% else %}
<div class="fieldWrapper">
{% if field.errors %}<div class="errorbox">{% endif %}
<p>{{ field.label_tag }}</p>
<p>{{ field }}</p>
<p></p>
{% if field.errors %}<p>{{ field.errors }}</p></div>{% endif %}
</div>
{% endif %}
{% endfor %}
it is a universal template for different forms. And now in one form:
class DifferentForm(forms.ModelForm):
id = forms.ModelChoiceField(queryset=DifferentItem.objects.all(), widget=forms.HiddenInput())
option = forms.ModelChoiceField(queryset=Option.objects.all(), widget=forms.HiddenInput())
(????)
temp = forms.BooleanField(widget=forms.HiddenInput(), required=False)
date = forms.SplitDateTimeField(widget=forms.SplitDateTimeWidget())
... etc
i want put an additional link in such a way as to show in this way:
<select ...>
<option>...</option>
</select>
(my additional link, button, text, whatever)
<input ...
How to do it?
The way I usually do this is in the template:
{% for field in itemForm %}
{% if field.name == "option" %}
Custom stuff I want to go before this field
{% endif %}
<!-- Regular field stuff goes here -->
{% if field.is_hidden %}
{{ field }}
{% else %}
<div class="fieldWrapper">
...
</div>
{% endif %}
<!-- End Regular Field Stuff -->
{% if field.name == "option" %}
Custom stuff I want to go after the field
{% endif %}
{% if field.name == "another_field_name"%}
Custom stuff I want to go after the field
{% endif %}
{% endfor %}

Display number of instances for each model in Django's admin index

I need to display number of objects at main django site admin page.
For example, in list of models I need to display
Elephants (6)
instead of
Elephants
I added this code to my model:
class Elephant(models.Model):
....
class Meta:
verbose_name_plural = 'Elephants ' + '(' + unicode(count_elephants()) + ')'
where count_elephants() calculates number of objects. The problem is that verbose_name_plural is calculated at server start and is not called when I delete/insert objects, so this calculated value becomes irrelevant.
Is it possible to do it in correct way?
Thanks!
Since verbose_name_plural is used in many other ways, a better way to do this will be to change the admin index view and admin template.
However, since the admin app can change, this is probably tied to a specific version of
django. I am attaching for example the modified admin taken from django 1.2.5.
(Note: I will use an in place replacement for the index method, but it will be probably better to subclass it instead of replacing the method)
As a start, copy from django/contrib/admin/sites.py the AdminSite.index method and it's required imports, and modify it to include counts (one line changed, look for 'THIS LINE WAS ADDED"). Add it to any of your admin.py files or somewhere else appropriate:
from django.utils.text import capfirst
from django import template
from django.shortcuts import render_to_response
from django.views.decorators.cache import never_cache
from django.utils.translation import ugettext as _
def index_with_count(self, request, extra_context=None):
"""
Displays the main admin index page, which lists all of the installed
apps that have been registered in this site.
"""
app_dict = {}
user = request.user
for model, model_admin in self._registry.items():
app_label = model._meta.app_label
has_module_perms = user.has_module_perms(app_label)
if has_module_perms:
perms = model_admin.get_model_perms(request)
# Check whether user has any perm for this module.
# If so, add the module to the model_list.
if True in perms.values():
model_dict = {
'name': capfirst(model._meta.verbose_name_plural),
'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())),
'perms': perms,
'count': model.objects.count(), # THIS LINE WAS ADDED
}
if app_label in app_dict:
app_dict[app_label]['models'].append(model_dict)
else:
app_dict[app_label] = {
'name': app_label.title(),
'app_url': app_label + '/',
'has_module_perms': has_module_perms,
'models': [model_dict],
}
# Sort the apps alphabetically.
app_list = app_dict.values()
app_list.sort(lambda x, y: cmp(x['name'], y['name']))
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
context = {
'title': _('Site administration'),
'app_list': app_list,
'root_path': self.root_path,
}
context.update(extra_context or {})
context_instance = template.RequestContext(request, current_app=self.name)
return render_to_response(self.index_template or 'admin/index.html', context,
context_instance=context_instance
)
site.index = never_cache(type(site.index)(index_with_count, site, AdminSite))
Now copy the django/contrib/admin/templates/admin/index.html file into admin/index.html in any of your templates folders to override the original template and modify it to show the counts:
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css" />{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block bodyclass %}dashboard{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block content %}
<div id="content-main">
{% if app_list %}
{% for app in app_list %}
<div class="module">
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
<caption>{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</caption>
{% for model in app.models %}
<tr>
<th scope="row">
{% if model.perms.change %}
{{ model.name }}
{% else %}
{{ model.name }}
{% endif %}
({{ model.count }})
</th>
{% if model.perms.add %}
<td>{% trans 'Add' %}</td>
{% else %}
<td> </td>
{% endif %}
{% if model.perms.change %}
<td>{% trans 'Change' %}</td>
{% else %}
<td> </td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
{% else %}
<p>{% trans "You don't have permission to edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
{% block sidebar %}
<div id="content-related">
<div class="module" id="recent-actions-module">
<h2>{% trans 'Recent Actions' %}</h2>
<h3>{% trans 'My Actions' %}</h3>
{% load log %}
{% get_admin_log 10 as admin_log for_user user %}
{% if not admin_log %}
<p>{% trans 'None available' %}</p>
{% else %}
<ul class="actionlist">
{% for entry in admin_log %}
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
{% if entry.is_deletion %}
{{ entry.object_repr }}
{% else %}
{{ entry.object_repr }}
{% endif %}
<br/>
{% if entry.content_type %}
<span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span>
{% else %}
<span class="mini quiet">{% trans 'Unknown content' %}</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endblock %}
This will do it.
(You will still need to modify the app_index view to see the counts correctly in the app index pages, I leave this as an exercise to you :-)
A 2022 update using Django 4.0
Subclass the default admin site, see the official Django doc on subclassing the AdminSite.
in the subclass, overwrite the _build_app_dict() method to add count in its model_dict as in:
model_dict = {
"name": capfirst(model._meta.verbose_name_plural),
"object_name": model._meta.object_name,
"perms": perms,
"admin_url": None,
"add_url": None,
"count": model.objects.count(), # THIS IS ALL YOU NEED TO ADD
}
Override the default admin site for your project with the subclass that we have created and optimized. see the official Django doc overriding the default admin site.
Override the app_list.html template if you haven't already. Inside your app_list.html template, you can now use the model.count variable like so {{ model.count }}. see the official Django docs on overriding templates.