Show ManyToManyField attributes of a given model in template - django

How can I show in a template the attributes related by a ManyToManyField of a model?
models.py
class Group(models.Model):
name = models.CharField()
user = models.ManyToManyField(User, related_name='user_group')
class Team(models.Model):
name = models.CharField()
group = models.ManyToManyField(Group, related_name='group_team')
views.py
class Index(ListView):
template_name = 'App/index.html'
model = User
def get_queryset(self):
return User.objects.filter(...)
template
{% for user in user_list %}
{{ user.username }}
{{ user.user_group.name }}
{{ user.user_group.group_team.name }}
{% endfor %}
I can show the username of the user but not the rest of the fields.

Since a ManyToMany relationship may have many objects all I had to do is iterate over them:
{% for group in user.user_group.all %}
{{ group.name }}
{% endfor %}
Update: As Todor suggests adding prefetch_related avoid unnecessary database queries
User.objects.filter(...).prefetch_related('user_group')

Related

Display foriegnkey fields in Django template for a CreateView

I am trying to display a checklist in the CreateView using the values in the ForeignKey fields for descriptions.
models.py
class Structure(models.Model):
name = models.CharField(max_length = 30)
description =models.CharField(max_length = 300, null=True, blank=True)
def __str__(self):
return self.name
class SelectedFramework(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
structure = models.ForegignKey(Structure)
selected = models.BooleanField(default = False)
views.py
class FrameworkCreateView(generic.CreateView):
model = SelectedFramework
fields =['structure', 'selected']
template_name = 'catalogue/structure.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super(FrameworkCreateView, self).form_valid(form)
structure.html
{% extends 'catalogue\base.html' %}
{% block container %}
<h2>{% block title %}Structures{% endblock title %}</h2>
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
<div class="col-sm-10">{{form.structure}} {{form.selected}}</div><br>
{% endfor %}
</div>
</form>
{% endblock %}
The code above works but will display the ForeignKey 'structure' as a dropdown list with the values of __str__. Is there a way to display string for structure.name and structure.description with the checkbox from selected in the CreateView?
In your template use:
{{ form.structure.name }}
{{ form.structure.description}}
You can write custom form, override the save method and create Structure object manually there:
class FrameworkForm(forms.ModelForm):
structure_name = forms.CharField(required=True)
structure_description = forms.CharField(required=False)
class Meta:
model = SelectedFramework
fields = [
'structure_name', 'structure_description', 'selected'
]
def save(self, commit=False):
instance = super(FrameworkForm, self).save(commit=False)
structure = Structure(
name=self.cleaned_data.get('structure_name'),
description=self.cleaned_data.get('structure_description')
)
structure.save()
instance.structure = structure
instance.save()
return instance
Also add form_class = FrameworkForm to your view instead of fields = ['structure', 'selected']
EDIT:
Perhaps you want something like this:
<ul>
{% for structure in form.fields.structure.choices.queryset %}
<li>{{ structure.name }} - {{ structure.description }}</li>
{% endfor %}
</ul>
If you want to get fields by iterating in the template. You have to use-
{% for field in form %}
{{ field }}
{% endfor %}
don't have to use any dot notation to get the field. If you want to get the label of the field you can use {{ field.label}} usually before {{field}}

django-filter: How to filter by field A but display field B as corresponding filter option/text?

I have a Django Post model with a fk to a Category model with a category name and a category slug. Now I filter these posts by category via django-filter and the LinkWidget. That's fine and worked via name="categories__name" out of the box:
# models.py
class Category(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
class Post(models.Model):
categories = models.ManyToManyField(Category)
# filters.py
class PostFilter(django_filters.FilterSet):
categories = django_filters.AllValuesFilter(
name="categories__name",
label="Categories",
widget=LinkWidget(),
)
class Meta:
model = Post
fields = ['categories',]
# template.html
<h2>Filter</h2>
{% for choice in filter.form.categories %}
{{ choice }}
{% endfor %}
<h2>Posts</h2>
<ol>
{% for obj in filter.qs %}
<li>{{ obj.title }}</li>
{% endfor %}
But now I would like to eliminate the categories__name as the GET parameter and use categories__slug instead - but keep the more human readable categories__name as the link text in the LinkWidget - but I have no idea how to achieve this, any hints?
Some renderd html snippets:
Filtering via name=categories__slug:
<ul id="id_categories">
<li>audio-and-video</li>
<li>bits-bytes</li>
<li>foo-and-bar</li>
</ul>
Filtering via name=categories__name:
<ul id="id_categories">
<li>Audio and Video</li>
<li>Bits/Bytes</li>
<li>Foo and Bar</li>
</ul>
But what I want is (pseudocode):
<li>{{ category.name }}</li>
... and rendered:
<ul id="id_categories">
<li>Audio and Video</li>
<li>Bits/Bytes</li>
<li>Foo and Bar</li>
</ul>
Perhaps it's easier to review this issue in a minimal, complete django project, so I made one: https://gitlab.com/tombreit/django-filter-demo
Versions:
Python 3.5.3
Django (1.10.7)
django-filter (1.0.4)
Try this solution:
class CustomAllValuesFilter(django_filters.ChoiceFilter):
#property
def field(self):
qs = self.model._default_manager.distinct()
qs = qs.order_by(self.name).values_list(self.name, self.extra['choice_name'])
del self.extra['choice_name']
self.extra['choices'] = list(qs)
return super(CustomAllValuesFilter, self).field
class PostFilter(django_filters.FilterSet):
categories = CustomAllValuesFilter(
name="categories__slug",
label="Categories",
widget=LinkWidget(),
choice_name="categories__name"
)
class Meta:
model = Post
fields = ['categories',]

Django - get in template reverse related many to many field

I have 3 models, Entry model and Category model, and I have created intermediate model CategoryEntry.
class Entry(models.Model):
entry_text = models.TextField()
user = models.ForeignKey(User)
class Category(models.Model):
user = models.ForeignKey(User)
category_text = models.CharField(max_length=200)
entries = models.ManyToManyField(Entry, through='CategoryEntry')
class CategoryEntry(models.Model):
category = models.ForeignKey(Category)
entry = models.ForeignKey(Entry)
viewed = models.BooleanField(default=False)
I have created a View with get_queryset method like this
def get_queryset(self):
order_by = self.request.GET.get('order_by')
if not order_by:
order_by = '-pub_date'
return Entry.objects.filter(category__id=self.kwargs.get('category_id', None), category__user__id=self.request.user.id).order_by(order_by)[:].select_related('user')
I have category ID from kwargs. The problem is - how to get every entries related field "viewed", so I can show its value in template. For example related User field I can get like
{% for entry in entries %}
{{ entry.entry_text }}
{{ entry.user.name }}
{% endfor %}
What I need is to access "viewed" field something like "entry.categoryentry.viewed"
Tried prefetch_related, but doesn't seem to work or don't get how to know right name to access field in template
Entry.objects.filter(category__id=self.kwargs.get('category_id', None), category__user__id=self.request.user.id).order_by(order_by)[:].select_related('user').prefetch_related(Prefetch("categoryentry_set", queryset=CategoryEntry.objects.filter(category__id=self.kwargs.get('category_id', None))))
Thanks!
You would do:
{% for entry in entries %}
{{ entry.entry_text }}
{{ entry.user.name }}
{% for ce in entry.catalogentry_set.all %}
{{ce.category.category_text}}
{# or whatever.. #}
{% endfor %}
{% endfor %}
Basically, if no related_name is present, you could access the reverse lookup element by lowercase model name + _set - which returns a queryset manager object.

many-to-many select_related in django class-based list view

We have an example model:
#models.py
class Author(models.Model):
name = models.CharField(max_length=255)
class Book(models.Model):
name = models.CharField(max_length=255)
author = models.ManyToManyField(Author, blank=True)
def get_authors(self):
return self.authors.all().order_by('id').values_list('name')
#views.py
class BooksView(ListView):
model = Book
def get_queryset(self):
q = Book.select_related('authors').all()
#template
{% for book in books %}
{{ book.name }} ({%for author in book.get_authors %} {{ author }} {% endfor %}
{% endfor %}
When I try to get data from template using get_authors function, I see multiple SQL queries that dramatically reduce performance (SQL works about 5sec). Is it possible to reduce queries? Now I see SQL query for each author in cycle.
M2M uses prefetch_related not select_related.
Fix the Model (there are different ways to do what you want to do):
Your Model:
class Book(models.Model):
name = models.CharField(max_length=255)
author = models.ManyToManyField(Author, blank=True)
def get_authors(self):
if self.authors:
return '%s' % " / ".join([author.name for author in self.authors.all()])
Fix your view:
class BooksView(ListView):
"""
Note that default Django views use object_list for the context,
in order to use books, you need to define context_object_name.
"""
context_object_name = "books"
"""
You don't need to override the queryset for this kind of operation.
Just define the queryset attribute of the CBV.
"""
queryset = Book.objects.prefetch_related('authors')
Then in your template:
#template
{% for book in books %}
{{ book.name }} {{ book.get_authors }}
{% endfor %}

Django filtering child objects in class-based generic list view

Good day for all!
My app used django class-based generic list view. I have two model objects: Books and Publishers that linked via foreign key (code below). I want to use ListView to show publishers with their books, but filter books (get only active books, owned by current user)
Additional info: I don't want to use filter in template if it's possible.
Additional info 2: I can't use filter via define in model class because I need access to request object
code
models.py
class Publisher(models.Model):
name = models.CharField(max_length=255)
class Book(models.Model):
name = models.CharField(max_length=255)
active = models.BooleanField(default=True)
publisher = models.ForeignKey(Publisher, related_name='books')
owner = models.ForeignKey(User)
views.py
class ListBooksByPublisher(ListView):
model = Publisher
template_name = 'list.html'
context_object_name = 'books'
list.html
{% for publisher in publishers %}
{{ publisher.name }}
{% for book in publisher.books.all %}
{{ book.name }}
{% endfor %}
{% endfor %}
Any help much appreciated!
you need to overwrite the get_queryset method on the view to return your custom queryset
For example:
class ListBooksByPublisher(ListView):
....
def get_queryset(self):
return self.model.objects.filter(blablabla))
Hope this helps
You may write your custom filter, which returns list of books for a publisher.
yourapp/templatetags/my_filters.py:
from django import template
register = template.Library()
#register.filter
def get_books(publisher):
return publisher.book_set.filter(YOUR_CUSTOM_FILTER)
Your template:
{% load get_books from my_filters %}
...
{% for publisher in publishers %}
{{ publisher.name }}
{% for book in publisher|get_books %}
{{ book.name }}
{% endfor %}
{% endfor %}
Another way is to pass extra data to your view:
class ListBooksByPublisher(ListView):
...
def get_context_data(self, **kwargs):
context = super(ListBooksByPublisher, self).get_context_data(**kwargs)
context['publishers_with_books'] = your_custom_data_structure
return context
#views
class ListBooksByPublisher(ListView):
model = Publisher
template_name = 'list.html'
context_object_name = 'publishers'
#tmp
{% for publisher in publishers %}
{{ publisher.name }}
{% for book in publisher.book_set.all %}
{{ book.name }}
{% endfor %}
{% endfor %}