I am trying to display ManyToMany field on the template in reversed order.
Here is what I mean:
I managed to display ManyToMany field on template when ManyToMany field was a field in model used so for example:
<br/>{% for tag in post.tag.all %}{{ tag }}<br/>{% endfor %}
will display all of the tags(meaning categories) that the post belongs to based on this model:
class Post(models.Model):
tag = models.ManyToManyField(Tag,blank=True,null=True,related_name='tag')
Now I want something opposite - display authors of the post when ManyToMany field is in the Author model (Post model above stays the same):
class Person(models.Model):
post=models.ManyToManyField(Post,blank=True,null=True,related_name='post')
I am quite sure it has something to do with Related Object Reference ( https://docs.djangoproject.com/en/2.2/ref/models/relations/)
Just can not make it work.
I have tried the following on the template.
{% for post in posts %}
{% for author in post.person_set.all %}{{author}}<br/>{% endfor %}
{% endfor %}
Also, shall I do this kind of searches on the template like above or is it a better practice put this kind of searches in views...resourcewise.
Thanks for help.
You have a misunderstanding on what the related_name= parameter [Django-doc] does. Like the documentation says:
The name to use for the relation from the related object back to this one. (...)
So it is the name of the relation in reverse. In order to make your models "sound", you thus should name it like:
class Person(models.Model):
posts = models.ManyToManyField(Post, blank=True, null=True, related_name='authors')
It also makes sense here to use the plural, so posts instead of post.
In that case, you thus can render this with:
{% for post in posts %}
{% for author in post.authors.all %}{{author}}<br/>{% endfor %}
{% endfor %}
Note that if you want to render all the values for ManyToManyFields, you better use .prefetch_related(..) in the queryset to prefetch the Person,s otherwise rendering the template will result in a lot of extra queries.
Django allows overwriting verbose_name and verbose_name_plural of a Model, which allows us to specify correct plural forms of Model names for non-English languages (for example Kandydaci as a plural of Kandydat instead of the default Kandydats which looks weird in Polish).
However, this is far from enough for languages with grammatical cases. For example, Django Admin gleefully displays us something like that:
(Where Zaznacz kandydat do zmiany stands for Select kandydat to change - Kandydat is a name of a Model)
This is incorrect. In this sentence the model name should have been displayed in accusative case, which is kandydata. However, I cannot simply specify that verbose_name of this Model is Kandydata, since would also affect all places where nominative case is expected instead - I've found one such place so far, and this is the heading of the table column:
This is incorrenct since the table heading should be called Kandydat not Kandydata.
How to fix this?
The string 'Select <verbose_name> to change' is declared inside the admin's main view
self.title = title % force_text(self.opts.verbose_name)
as a {{ title }} template variable
{% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}
So, touching the built-in view is out of question. But there is another way of doing it! You see, Django Admin templates are full of {% block %}s acting as placeholders in order to override them. Read more at the official docs about overriding Admin templates.
So, to your question.
Under your project's root directory create a directory templates (if it doesn't exists already).
Under my_project/templates/ create another one, admin.
Under my_project/templates/admin/ create a file change_list.html.
Inside my_project/templates/admin/change_list.html put these:
{% extends 'admin/change_list.html' %}
{% load myutils_filters %}
{% comment %}
You have to create an app that will hold all common tags/filters
that are re-usable across your entire project.
The model class itself is passed as cl.model
{% endcomment %}
{% block content_title %}{{ block.super|to_accusative:cl.model }}{% endblock %}
Inside your myutils/templatetags/myutils_filters.py file put these:
from django import template
from django.utils.html import format_html
from django.utils.encoding import force_text
register = template.Library()
#register.filter()
def to_accusative(value, model):
verbose_name = model._meta.verbose_name # declared in Meta
new_name = force_text(model.accusative case()) # a custom class method (lives in your Model)
return format_html(value.replace(verbose_name, new_name))
Finally, under your app's models.py, under each model class define a classmethod method like this:
from django.db import models
from django.utils.translation import ugettext_lazy as _
class MyModel(models.Model):
# model fields here
def __str__():
return self.a_field
class Meta:
verbose_name = 'verbose name'
verbose_name_plural = 'verbose name plural'
# accusative_case = 'accusative name case' # BAD. Raises error. Implement a class method intead ;)
#classmethod
def accusative_case(cls):
# You'll define the Polish translation as soon as you run makemessages
return _('Name in english of the accusative case')
Run manage.py makemessages, manage.py compilemessages, reload your browser and voila!
Note: The above may look a little hackish but it works brilliant. Tested locally and works.
I have something like the following:
class Destination(models.Model):
name = models.CharField
picture = models.ImageField
def __unicode__(self):
return u"%s" % self.name
class Vacation(models.Model):
destination = models.ForeignKey(Destination)
When creating the model in my Django Admin interface, I'd like my Destinations to be displayed as radio buttons with the Destination name and Destination picture.
I'm using a custom add_form template so displaying radio buttons with destination name is no problem, but including the picture is difficult.
I would like to leave __unicode__(self) as-is, I only need the picture returned with the object in this admin view. Also I don't want to inline the object.
Any advice on how to do this (including how to incorporate it into the template) would be great!
EDIT: This SO post comes very close to what I need, but I would like to access the individual choice data instead of parsing it from a modified label.
This is not an admin-specific answer, but I think it should work in admin if you can use a custom template for the form.
You could make a modified widget (probably as a subclass of an existing Django widget), that send extra fields from the model to a custom widget template.
You could also render the form manually in the template where it's displayed, and make an inclusion tag that fetches any extra information using the id of your destination object, which is passed as the value of the option.
For example:
your_template.html
{% load destinations %}
{% for opt in form.destination %}
{{ opt.tag }}
{% destination opt.data.value %}
{% endfor %}
destinations.py (in your_app/templatetags)
from django import template
from your_app.models import Destination
register = template.Library()
#register.inclusion_tag('your_app/destination-option.html')
def destination(id):
destination=Destination.objects.filter(id=int(id)).first()
return {'destination':destination}
destination-option.html
<!-- any formatting you prefer -->
{{destination.title}}
<img src="{{destination.picture.image_url}}">
I use mongoengine, and love it very.
now I work it with django, and I get a problem with it
how to set property for ListField
e.g.
this was my model
class Bussiness(Document):
tags = ListField(StringField())
and I want to use it on template like this:
{% for tag on bussiness.tags %}
{{ tag.url }}
{% endfor %}
but the tag.url I don't want to save it to database, just hope to produce it on model level, and make tags only some string on database.
And if I hard code it on template, I have to write it everywhere, that was I disgust.
This wont work as tag is just a string (you have it defined as a StringField).
You could have tag as an embedded document with url being a property eg:
class Tag(EmbeddedDocument):
name = StringField()
#property
def url(self):
return "http://my-ace-site.come/businesses/%s/"
class Business(Document):
tags = ListField(StringField())
If, for a field that you want to filter by, you have more than ~10 values, the filtering sidebar starts to be ugly and harder to use.
I'm looking for a solution to replace the <li> with a dropdown selection (combobox) or something similar that will solve the same problem.
Thanks #beholderrk, #gediminas and #jk-laiho! I packaged this into a reusable app.
Install:
pip install django-admin-list-filter-dropdown
Enable in settings.py:
INSTALLED_APPS = (
...
'django_admin_listfilter_dropdown',
...
)
Use in admin.py:
from django_admin_listfilter_dropdown.filters import (
DropdownFilter, ChoiceDropdownFilter, RelatedDropdownFilter
)
class EntityAdmin(admin.ModelAdmin):
...
list_filter = (
# for ordinary fields
('a_charfield', DropdownFilter),
# for choice fields
('a_choicefield', ChoiceDropdownFilter),
# for related fields
('a_foreignkey_field', RelatedDropdownFilter),
)
Here's what it looks like:
I cannot comment answers so I'll add to beholderrk's answer here.
create a new template called dropdown_filter.html or similar
copy the code of filter.html from feincms to dropdown_filter.html
create a new filter class in filters.py:
from django.contrib.admin.filters import AllValuesFieldListFilter
class DropdownFilter(AllValuesFieldListFilter):
template = 'admin/dropdown_filter.html'
now you can use this filter in your admin class:
class SomeAdmin(admin.ModelAdmin):
# ...
list_filter = (('country', DropdownFilter),)
Works great!
Use filter.html from feincms
{% load i18n %}
<script type="text/javascript">var go_from_select = function(opt) { window.location = window.location.pathname + opt };</script>
<h3>{{ title }}</h3>
<ul class="admin-filter-{{ title|cut:' ' }}">
{% if choices|slice:"4:" %}
<li>
<select style="width: 95%;"
onchange="go_from_select(this.options[this.selectedIndex].value)">
{% for choice in choices %}
<option{% if choice.selected %} selected="selected"{% endif %}
value="{{ choice.query_string|iriencode }}">{{ choice.display }}</option>
{% endfor %}
</select>
</li>
{% else %}
{% for choice in choices %}
<li{% if choice.selected %} class="selected"{% endif %}>
{{ choice.display }}</li>
{% endfor %}
{% endif %}
</ul>
An easy option would be to use django-grappelli, which replaces all the filters with drop downs.
You can copy the admin templates from the django installation into you templates/admin folder in your project.
Then you will need to do any of 2 things in the forms or templates you want to show your outputs in:
If you are working with a form, in that you would like the list choices to be posted back to a database, you would in your model.py, on the field you have your choices, put in some this like this:
choice = forms.IntegerField(widget=forms.Select(choices=CHOICES))
If it is just to display on a page, then you will output on a template tag something like this:
<select>
{% for choices in object.details.all %}
<option> {{ object.choice }} </option>
{% endfor %}
</select>
http://djangosuit.com/ also offers dropdowns for list filters.
I am not a fan of all solutions provided up to now.
Why? If, for a field that you want to filter by, you have more than 10 values, a listview box isn't that handy, too. I advice to use the standard search field capability of django admin which will show you a search field:
class BooksAdmin(admin.ModelAdmin):
list_display = ('ISBN', 'title')
search_fields = ('ISBN',)
# instead of: list_filter = ('ISBN',)
ordering = ('title',)
The best solution is to create a new template in admin/filter.html and implement the HTML code suggested by #beholderrk. Just implemented it for a client and it works great.
Problem with DropdownFilter and RelatedDropdownFilter is that it loses the proper display. Instead of the translated strings for Charfield(choices=xxx), it will show True, False and so on.
Could you please give a complete example. it shows like before.
here is my code
from django.contrib import admin
from pages.models import Post, Device, DeviceType, DeviceModel, Ipaddress, DeviceGroup, Location,Department,Comment
from django_admin_listfilter_dropdown.filters import DropdownFilter, RelatedDropdownFilter
class CommentInline(admin.TabularInline):
model = Comment
class IpaddressAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('ipaddress',)}
# model=Ipaddress
search_fields = ['ipaddress', ]
#
list_display = ('ipaddress', 'machinename', 'user', 'department','location',)
list_filter = (
('user', DropdownFilter),
('department', RelatedDropdownFilter),
('location', RelatedDropdownFilter),
)
Here is the screenshot
I was struggling with the same problem some few weeks back. So this answer might be useful to some developers from the future.
I managed to solve the problem by writing a custom template.html
I have bundled the code in an amazing package now that does the same for you, here's the link.
Here's how you can implement a Searchable Dropdown in place of the default List:
1. Installation:
pip install django-admin-searchable-dropdown
This command will install the latest version of the package in your project.
Now, include the package in your project by adding admin_searchable_dropdown to your INSTALLED_APPS inside settings.py file.
2. Usage:
Let's say you have following models:
from django.db import models
class CarCompany(models.Model):
name = models.CharField(max_length=128)
class CarModel(models.Model):
name = models.CharField(max_length=64)
company = models.ForeignKey(CarCompany, on_delete=models.CASCADE)
And you would like to filter results in CarModelAdmin on the basis of company. You need to define search_fields in CarCompany and then define filter like this:
from django.contrib import admin
from admin_searchable_dropdown.filters import AutocompleteFilter
class CarCompanyFilter(AutocompleteFilter):
title = 'Company' # display title
field_name = 'company' # name of the foreign key field
class CarCompanyAdmin(admin.ModelAdmin):
search_fields = ['name'] # this is required for django's autocomplete functionality
# ...
class CarModelAdmin(admin.ModelAdmin):
list_filter = [CarCompanyFilter]
# ...
After following these steps you may see the filter as:
This is how the list filter is rendered in the form of a dropdown when the package is used
And the dropdown filter is also Searchable
Features Offered:
If you have large fields in Admin Filter, the sidebar gets widened, this package provides you the same list in a dropdown format, hence, no such hassle.
If you have more than, say 20, field items, list filter is now a long side-pane, just ruining the admin interface. The Dropdown filter fixes that.
The Dropdown is also "Searchable", with an input text field (which utilizes Django's own auto_complete functionailty), so as long as the Django version you are using is greater than 2.0, you should be fine.
You can customize other list_filters you may have, like change the Title above the dropdown, or a custom Search logic etc.
You can customize Widget Texts to display in the Dropdown Option to use something other than the default str(obj)