I have two models (Taxonomia and Distribucion) which are the following:
# models.py file
class Taxonomia(models.Model):
id_cactacea = models.AutoField(primary_key=True)
subfamilia = models.CharField(max_length=200)
class Distribucion(models.Model):
id_distribucion = models.AutoField(primary_key=True)
localidad = models.TextField(null=True, blank=True)
taxonomia = models.ForeignKey(Taxonomia, on_delete=models.CASCADE, default=1)
As you can see in Distribucion there is a one to many relationship with the Taxomia table.
Implement the two models in the "admin.py" file so that you can edit the Distribucion table from Taxonomia
class DistribucionInline(admin.TabularInline):
model = Distribucion
extra = 0
class TaxonomiaAdmin(admin.ModelAdmin):
actions = None # desactivando accion de 'eliminar'
list_per_page = 20
search_fields = ('genero',)
radio_fields = {"estado_conservacion": admin.HORIZONTAL}
inlines = [DistribucionInline]
admin.site.register(Taxonomia, TaxonomiaAdmin)
In turn, the file "view.py" renders the Taxonomia table as follows:
from repositorio.models import Taxonomia, Distribucion
class CactaceaDetail(DetailView):
model = Taxonomia
template_name = 'repositorio/cactacea_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['distribuciones'] = Distribucion.objects.all()
return context
I tried to access to context ['distribuciones'] information from the template as follows without getting any results:
{% for obj in object.distribuciones %}
{{ obj.localidad }}
{% endfor %}
OBS: For each Taxonomia element there will be four elements from the Distribucion table, so I need to use a FOR loop
Is the way I add the information from the Taxonomia table in the "CactaceaDetail" view correct?
Is the way I read the information in the template correct?
How could I visualize all the information that "CactaceaDetail" sends to the template using the DJANGO shell so that I can debug better in the future?
Thank you.
Try removing the "object" from your for loop in the template:
{% for obj in distribuciones %}
{{ obj.localidad }}
{% endfor %}
The reason is because you are passing distribuciones in the regular context, not as part of the class object so you can't reference it with object.distribuciones.
I'd like to figure out a way to use a key-value lookup for a second model within a Django for loop.
This question on dictionary key-value loops in templates is on the right track, but I am using two normalized data models. There is a 'Parent' data object (EventDetail) that contains the relevant link and 'Child' data object (DateEvent) that has date-specific information with data on user actions.
I have a template tag, link_if_exists, that is being duplicated many times. Django-debug-toolbar tells me this is being duplicated 76 times right now. This 'duplicate' message is, itself, duplicated many times.
This is what I have now:
app_extras.py
#register.filter()
def link_if_exists(title):
"""Return a link if it exists"""
event_link = None
try:
event_link = EventDetail.objects.filter(title=title).values_list('link', flat=True)[0]
except IndexError:
pass
if event_link != "":
return event_link
return False
template.html
{% load 'app_extras' %}
{% for item in child_date_items %}
{% if item.title|link_if_exists %}
{{item.title}}
{% endif %}
{% endfor %}
models.py
class DateEvent(models.Model)
title = models.CharField(max_length=250)
event_date = models.DateField(default=date.today)
event_datetime = models.DateTimeField(auto_now=False)
class EventDetail(models.Model)
title = models.CharField(max_length=250)
link = models.URLField(max_length=200, default="", blank=True)
views.py
class ProblemView(TemplateView):
template_name = "template.html"
def get_context_data(self, **kwargs):
context = super(ProblemView, self).get_context_data(**kwargs)
date_today = utils.get_date_today()
child_date_items = DateEvent.objects.filter(event_date=date_today)
context['child_date_items'] = child_date_items
return context
Django-debug-toolbar output
SELECT link FROM
table WHERE title='title'
...
| duplicated 74 times
Something like this is what I am after:
Add 'event_detail' to views.py
class NewView(TemplateView):
template_name = "template.html"
def get_context_data(self, **kwargs):
context = super(NewView, self).get_context_data(**kwargs)
date_today = utils.get_date_today()
child_date_items = DateEvent.objects.filter(event_date=date_today)
context['child_date_items'] = child_date_items
event_detail = EventDetail.objects.all()
context['event_detail'] = event_detail
return context
Lookup title as key to get 'link' value in ideal_template.html
{% for item in child_date_items %}
{% if event_detail[item.title]['link'] %}
{{item.title}}
{% endfor %}
{% endfor %}
I don't know if this functionality exists so I am open to other suggestions. I am also open to computing this in views.py and iterating over a common object in the template. I understand that I could duplicate the link data and just add a link column in the DateEvent model, but that seems wasteful and I'd like to avoid that if possible. This isn't the only field I need this type of logic, so adding everything to the Child object would take up a lot of extra space in the database.
You need to do some work on your models. If there is a one-to-many relationship between them (several DateEvents for one EventDetail), then rather than duplicating title manually in both of them and then linking both again manually, you should formalize the relationship at the model level:
class EventDetail(models.Model)
title = models.CharField(max_length=250)
link = models.URLField(max_length=200, default="", blank=True)
class DateEvent(models.Model)
event_detail = models.ForeignKey(EventDetail, on_delete=models.CASCADE)
event_date = models.DateField(default=date.today)
event_datetime = models.DateTimeField(auto_now=False)
Then you can refer to the link field from any DateEvent object without any conditionals and duplicate queries (if you use select_related() properly).
So if you're working with DateEvents, you'd use:
date_events = (
DateEvent.objects
.filter(event_date=date_today)
.select_related('event_detail')
)
Then, in the template:
{% for item in child_date_items %}
{{item.event_detail.title}}
{% endfor %}
I have a django based application where I want to create a form out of key, value pairs from a model. The `Child' model consists of the following rows of data:
(<parent 1>, 'component 1', 'dummy content 1'),
(<parent 1>, 'component 2', 'dummy content 2'),
Here is are my models:
# models.py
class Parent(models.Model):
class Meta:
verbose_name = 'Parent'
db_table = "parent"
title = models.CharField(max_length=28)
def __str__(self):
return self.title
class Child(models.Model):
class Meta:
verbose_name = 'Child'
db_table = "child"
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
key = models.CharField(max_length=20)
value = models.TextField()
def __str__(self):
return self.parent
Following is the direct model to form mapping I am currently using for my other forms to keep it straight forward and simple
# forms.py
class MyForm(ModelForm):
class Meta:
model = Child
fields = () # fields go here
Then I pass this form to my view. The view page_view takes pk of the parent, gets the parent and passes it to the form. The form is then passed on to the template parent_view.html via the view.
# views.py
#login_required
def page_view(request, parent_pk):
parent = get_object_or_404(Parent, pk=pk)
my_form = MyForm(request.POST, instance=parent)
return render(request, 'parent_view.html', {
'parent': parent,
'my_form': my_form,
})
In the template I render the form like this:
<!-- page_view.html -->
{{ my_form }}
However, I would also like to write the html for this manually to add any design changes locally. I would like the forms.py MyForm to construct a form from the model by collecting key, value pairs for the provided parent.
So it should render it like this:
<form action=''>
<label for='component_1'>component 1</label>
<textarea name='component_1' type='text'>dummy content 1</textarea>
<label for='component_2'>component 2</label>
<textarea name='component_2' type='text'>dummy content 2</textarea>
</form>
But I can't seem to get my head around how to handle that in the `MyForm'. I have looked around a couple of solutions over stackoverflow but none of them point me in the right direction for this problem. If anyone has any ideas I would highly appreciate. Thanks in advance.
If there are multiple Child instances, then a single form will not be of much use, you will have to use a formset (a model formset to be precise).
As per the docs,
A formset is a layer of abstraction to work with multiple forms on the same page
# forms.py
# You can provide a text area widget for the field that you want to be displayed as a text area
class MyForm(ModelForm):
class Meta:
model = Child
fields = () # fields go here
widgets = {
'field_name': forms.Textarea(attrs={'cols': 80, 'rows': 3}),
}
ChildFormset = forms.modelformset_factory(Child, ChildForm, exclude=[], extra=0)
Then in your views, you can pass a queryset of all the objects that you want in your form
# views.py
from .forms import ChildFormset
#login_required
def page_view(request, parent_pk):
parent = get_object_or_404(Parent, pk=pk)
child_queryset = parent.child_set.all()
if request.method == 'GET':
child_formset = ChildFormset(queryset=child_queryset)
return render(request, 'parent_view.html', {
'parent': parent,
'my_formset': child_formset,
})
else:
child_formset = ChildFormset(request.POST, queryset=child_queryset)
if child_formset.is_valid():
for form in child_formset:
form.save()
# ... Do whatever else you want to do with the data
In your templates, you will then have to traverse through all the form objects in the formset. Then you can display them in whatever way you want to.
# parent_view.html
{{ child_formset.management_form }}
{% for form in child_formset %}
<div class="hidden">{{ form.id }}</div>
{% for field in form.visible_fields %}
{{ field }}
{% endfor %}
{% endfor %}
NOTE: The Foreign Key field will be displayed as a drop down for the user to select a parent object from the list of parent objects.
I am trying to get django to display posts from a single catgory on that category's page, instead, it displays all the categories and the posts under each category in the template.
Here are my models (I trimmed them down):
class Category(models.Model):
...
title = models.CharField(max_length=255, verbose_name="Title", null=True, blank=True)
slug = models.SlugField(max_length=200, unique=True)
class Petition(models.Model):
...
category = models.ManyToManyField(Category, verbose_name="Category", related_name='petitions')
...
views.py:
class CategoryMixin(object):
def get_context_data(self, **kwargs):
context = super(CategoryMixin, self).get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
class CategoryView(CategoryMixin, generic.ListView):
model = Category
categories = Category.objects.all() # this will get all categories, you can do some filtering if you need (e.g. excluding categories without posts in it)
queryset = Category.objects.all()
template_name = 'petition/category_list.html'
class CategoryIndexView(generic.ListView):
model = Category
template_name = 'petition/category.html'
context_object_name = 'category_list'
def get_queryset(self):
return Category.objects.prefetch_related('petitions').order_by('-created_on')[:10]
class PetitionIndexView(generic.ListView):
template_name = 'petition/home.html'
context_object_name = 'petition_list'
queryset = Petition.objects.order_by('-created_on')
def get_queryset(self):
queryset_list = Petition.objects.order_by('-created_on')
#SEARCH QUERY LOGIC
query = self.request.GET.get("q")
if query:
queryset_list = queryset_list.filter(
Q(title__icontains = query) |
Q(petition__icontains = query) |
Q(created_by__first_name__icontains = query) | #icontains = only text fields. Use text field within foreign key field
Q(created_by__last_name__icontains = query)
).distinct()
paginator = Paginator(queryset_list, 10) # Show 10 posts per page
page = self.request.GET.get('page')
try:
queryset = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
queryset = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
queryset = paginator.page(paginator.num_pages)
return queryset
context = {
'object_list' : queryset,
'title' : 'List'
}
urls.py:
url(r'^$',views.PetitionIndexView.as_view(), name="homepage"),
url(r'^petition/(?P<slug>[\w-]+)/$',views.DetailView.as_view(), name="detail"),
url(r'^categories/$', views.CategoryView.as_view(), name='category_list'),
url(r'^category/(?P<slug>[\w-]+)/$',views.CategoryIndexView.as_view(), name="category_detail"),
And my template (category.html for the CategoryIndexView) is in this format,
category.html:
{% for category in category_list %}
{% for petition in category.petitions.all %}
...
{{ petition.title }}
...
{% end for %}
{% end for %}
This shows posts in Category A, B and C in the same template.
With the views I set, how can I get it to show only posts for category A when category A is clicked?
Also, the ordering is not working properly. It is not adhering to the ordering I gave it in the view
You're using the wrong view class. A ListView is to show a list of instances of the class; so a CategoryIndexView would be to show the list of categories. But you don't want a list of categories; you want a single category, along with a list of its petitions. So, you should use a DetailView.
class CategoryDetailView(generic.DetailView):
model = Category
template_name = 'petition/category.html'
context_object_name = 'category'
def get_queryset(self):
return Category.objects.prefetch_related('petitions')
And the template is just:
{% for petition in category.petitions.all %}
...
{{ petition.title }}
...
{% end for %}
Edit: I changed the context_object_name from 'category_list' to 'category' and the template is returning the right posts now.
I'm guessing that every time you change categories, the whole loop executes again, without changing the contents of category_list (which initially is ["A", "B", "C"]). In that case, even though you click on the button to change category, the loops displays the contents of the categories "A", "B" and "C" again regardless.
Have you tried adding an if statement that checks in which category you are at the moment and only show the posts in that category?
{% for category in category_list %}
{% if category.name == ACTUAL_CATEGORY_TO_DISPLAY %}
{% for petition in category.petitions.all %}
...
{{ petition.title }}
...
{% end for %}
{% end if %}
{% end for %}
Edit:
As #Daniel Roseman pointed out in a comment, this is a quite inefficient solution. I've just posted it because I'm guessing if this is really what is happening to you.
If this is the case you shouldn't be doing this. One of the proper solutions is making a new request that retrieves all the posts for that given category every time you change it.
Anyhow, I'm just guessing here. For us to give you a better insight you should post the template code that does the category change.
UPDATE: Just found out that the ManyToManyField is causing the admin interface to crash, when a specific album is selected. I commented them out, commented out all references to it, reran makemigrations and migrate, and now the admin interface works again...which leaves me even farther away from making this "favorite" column work :( See this followup: Why is Django ManyToManyField causing admin interface to crash? Why is no through table being created?
Background: My goal is to make the "Favorite?" column in this webpage reflect the favorite albums of the currently-logged-in user, where each is either "no" or "yes", and is a clickable link to toggle the choice. (When not logged in, they will all be grey "n/a"-s.)
Therefore, for each album, there may be exactly zero or one "has favorited" entry per user. If the entry exists, they've favorited it. If it doesn't exist, they didn't.
Here is my Album model, with the favorited_by_users many-to-many column (full models.py at the bottom):
class Album(models.Model):
OFFICIALITY = (
('J', 'Major studio release'),
('I', 'Non-major official release'),
('U', 'Unofficial'),
)
title = models.CharField(max_length=70)
description = models.TextField(max_length=500, default="", null=True, blank=True)
pub_date = models.DateField('release date')
officiality = models.CharField(max_length=1, choices=OFFICIALITY)
is_concert = models.BooleanField(default=False)
main_info_url = models.URLField(blank=False)
thumbnail = models.FileField(upload_to=get_upload_file_name, blank=True, null=True)
#virtual field to skip over the through table.
songs = models.ManyToManyField("Song", through="AlbumSong")
favorited_by_users = models.ManyToManyField(User)
def __str__(self):
return self.title
class Meta:
#Default ordering is by release date, ascending.
ordering = ['pub_date']
I originally had this FavoriteAlbum model, but since it has no extra information beyond the foreign keys, it was recommended that I eliminate it in favor of the above many-to-many column.
class FavoriteSongs(models.Model):
user = models.ForeignKey(User)
song = models.ForeignKey(Song)
class Meta:
unique_together = ('user', 'song',)
def __str__(self):
return "user=" + str(self.user) + ", song=" + str(self.song)
What I need to do is a "left join" between album and user, where all albums are selected, and any favorites by the currently-logged-in user are joined to it (None if they haven't favorited it). I don't know what to do.
I've also been told about the extra() function to do this join. The currently-working query in the view's get_queryset() is
return super(AlbumList, self).get_queryset().order_by("pub_date")
(Full views.py below.) My current guess is this:
return super(AlbumList, self).get_queryset().order_by("pub_date").extra(select={"is_favorite": "favorited_by_users__id = " + str(request.user.id) })
But, while this doesn't crash, the value of each {{ is_favorite }} in the template is nothing (the empty string). This makes sense since there's nothing in the database yet, but what now? I have no idea if this is the correct Django query.
I want to add an item in the database to test this, with a manual SQL statement in postgres (not via a Django command yet), but how and where do I do this?
I've successfully run makemigrations and then migrate with this new m2m column (and without the FavoriteSongs model), but I see nothing in the database that represents the is-favorite value. There's no extra column in billyjoel_album, and no through table akin to billyjoel_favoritealbum. So where/how is this data stored in the database?
(Any other advice regarding this extra "favorite" column would be appreciated as well!)
Thanks.
models.py
from django.db import models
from django.contrib.auth.models import User
from time import time
def get_upload_file_name(instance, filename):
return "uploaded_files/%s_%s" % (str(time()).replace(".", "_"), filename)
class Album(models.Model):
OFFICIALITY = (
('J', 'Major studio release'),
('I', 'Non-major official release'),
('U', 'Unofficial'),
)
title = models.CharField(max_length=70)
description = models.TextField(max_length=500, default="", null=True, blank=True)
pub_date = models.DateField('release date')
officiality = models.CharField(max_length=1, choices=OFFICIALITY)
is_concert = models.BooleanField(default=False)
main_info_url = models.URLField(blank=False)
thumbnail = models.FileField(upload_to=get_upload_file_name, blank=True, null=True)
#virtual field to skip over the through table.
songs = models.ManyToManyField("Song", through="AlbumSong")
favorited_by_users = models.ManyToManyField(User)
def __str__(self):
return self.title
class Meta:
#Default ordering is by release date, ascending.
ordering = ['pub_date']
class Song(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(max_length=500, default="", null=True, blank=True)
length_seconds = models.IntegerField()
lyrics_url = models.URLField(default="", blank=True, null=True)
albums = models.ManyToManyField("Album", through="AlbumSong")
favorited_by_users = models.ManyToManyField(User)
def get_length_desc_from_seconds(self):
if(self.length_seconds == -1):
return "-1"
m, s = divmod(self.length_seconds, 60)
h, m = divmod(m, 60)
if(h):
return "%d:%02d:%02d" % (h, m, s)
else:
return "%d:%02d" % (m, s)
def __str__(self):
return self.name
class AlbumSong(models.Model):
song = models.ForeignKey(Song)
album = models.ForeignKey(Album)
sequence_num = models.IntegerField()
class Meta:
unique_together = ('album', 'sequence_num',)
unique_together = ('album', 'song',)
def __str__(self):
return str(self.album) + ": " + str(self.sequence_num) + ": " + str(self.song)
views.py
from .models import Album, Song, AlbumSong
from datetime import datetime, timedelta
from django.core.context_processors import csrf
from django.shortcuts import render, render_to_response
from django.views.generic import DetailView, ListView
from enum import Enum
def get_str_with_appended(string, between_if_str_non_empty, new_value):
if(len(string) == 0):
return new_value
else:
return string + between_if_str_non_empty + new_value
class PrependQuestionMark(Enum):
YES, NO = range(2)
def get_url_param_string_from_params(prepend_question_mark=PrependQuestionMark.YES, **kwargs_all_params):
param_list = ""
for key in iter(kwargs_all_params):
value = kwargs_all_params[key]
if(value is not None):
param_list = get_str_with_appended(param_list, '&', str(key) + "=" + str(value))
if(len(param_list) == 0):
return param_list;
if(prepend_question_mark == PrependQuestionMark.YES):
return "?" + param_list
else:
return param_list
class AlbumList(ListView):
model = Album
context_object_name = "albums"
#Derived from irc/#dango/tbaxter...START
def dispatch(self, request, *args, **kwargs):
#default to asc
self.sort_order = request.GET.get("sort_order", None)
self.sort_item = request.GET.get("sort_item", None)
self.csrf_token = csrf(request)["csrf_token"]
self.logged_in_user = request.user
#self.csrf_token = request.GET.get("csrf_token", None)
return super(AlbumList, self).dispatch(request, *args, **kwargs)
def get_queryset(self):
#Item zero in both is the default
#should be static global
asc_desc_list = ["asc", "dsc"]
sort_by_types = ["pub_date", "title"]
if(self.sort_order is None and self.sort_item is None):
#Use default ordering
return super(AlbumList, self).get_queryset()
#Custom ordering requested
sort_order = self.sort_order
sort_item = self.sort_item
if(sort_order is None or
sort_order not in asc_desc_list):
sort_order = asc_desc_list[0]
if(sort_item is None or
sort_item not in sort_by_types):
sort_item = sort_by_types[0]
order_minus = "" if sort_order == "asc" else "-"
return super(AlbumList, self).get_queryset().order_by(order_minus + sort_item).extra(select={"is_favorite": "favorited_by_users__id = " + str(self.logged_in_user.id) })
def get_context_data(self, **kwargs):
context = super(AlbumList, self).get_context_data(**kwargs)
context["sort_order"] = self.sort_order
context["sort_item"] = self.sort_item
context["url_params"] = get_url_param_string_from_params(
sort_item=self.sort_item,
sort_order=self.sort_order,
csrf_token=self.csrf_token)
return context
class AlbumDetail(DetailView):
model = Album
context_object_name = "album"
def dispatch(self, request, *args, **kwargs):
#default to asc
self.sort_order = request.GET.get("sort_order", None)
self.sort_item = request.GET.get("sort_item", None)
self.csrf_token = csrf(request)["csrf_token"]
return super(AlbumDetail, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
#Call the base implementation first to get a context
context = super(AlbumDetail, self).get_context_data(**kwargs)
#Add in the required extra info: album_songs, ordered by
#sequence_num
#select_related is to query the database for all songs at once, here
#in the view, to prevent the template from pin-pricking the database
#in each for loop iteration. For large datasets, this is critical.
context['album_songs'] = kwargs["object"].albumsong_set.order_by('sequence_num').select_related("song")
context["url_params"] = get_url_param_string_from_params(
sort_item=self.sort_item,
sort_order=self.sort_order,
csrf_token=self.csrf_token)
return context
album_list.html
{% extends "base.html" %}
{% load bj_filters %}
{% block title %}Billy Joel Album Browser{% endblock %}
{% block sidebar %}
<UL>
<LI>All albums</LI>
<LI>Admin</LI>
</UL>
{% endblock %}
{% block content %}
<TABLE ALIGN="center" WIDTH="100%" BORDER="1" CELLSPACING="0" CELLPADDING="4" BGCOLOR="#EEEEEE"><TR ALIGN="center" VALIGN="middle">
{% if user.is_authenticated %}
<TD>My profile (Logout)</TD>
{% else %}
<TD>Login to view your favorites</TD>
{% endif %}
</TR></TABLE>
<H1>Billy Joel Album Browser</H1>
<!--
<P>url_params={{ url_params }}</P>
-->
{% if albums.count > 0 %}
<P>Officiality: <IMG SRC="/static/images/major.jpg" height="20"/>=Major studio release, <IMG SRC="/static/images/minor.jpg" height="20"/>=Official release, <IMG SRC="/static/images/unofficial.jpg" height="20"/>=Unofficial</P>
<TABLE ALIGN="center" WIDTH="100%" BORDER="1" CELLSPACING="0" CELLPADDING="4" BGCOLOR="#EEEEEE"><TR ALIGN="center" VALIGN="middle">
<TD><B><U><a href="{% url 'album_list' %}?sort_item=title&sort_order=
{% if sort_item == 'pub_date' %}asc{% else %}
{{ sort_order|multival_to_str:'asc,dsc->dsc,asc,dsc' }}
{% endif %}
&csrf_token={{ csrf_token }}">Title</A></U></B><BR><I><FONT SIZE="-1">(click a title to view its song list)</FONT></I></TD>
<TD><B><U><a href="{% url 'album_list' %}?sort_item=pub_date&sort_order=
{% if sort_item == 'title' %}asc{% else %}
{{ sort_order|multival_to_str:'asc,dsc->dsc,asc,dsc' }}
{% endif %}
&csrf_token={{ csrf_token }}">Released</A></U></B></TD>
<TD>Officiality</TD>
<TD>Concert</TD>
<TD>Wiki</TD>
<TD>Favorite?</TD>
{% for album in albums %} <!-- No colon after "albums" -->
</TR><TR>
<TD VALIGN="top">
{% if album.thumbnail %}
<img src="/static/{{ album.thumbnail }}" width="25"/>
{% else %}
<img src="/static/images/white_block.jpg" width="25"/>
{% endif %}
{{ album.title }}
{% if album.description %}
<BR/><FONT SIZE="-1"><I>{{ album.description|truncatewords:10 }}</I></FONT>
{% endif %}
<TD>{{ album.pub_date|date:"m/y" }}</TD>
<TD><IMG SRC="/static/images/{{ album.officiality|multival_to_str:"J,I,U->major,minor,unofficial,broken_image"}}.jpg" height="20"/></TD>
<TD>{{ album.is_concert|yesno:"Yes,No" }}</TD>
<TD>Wiki</TD>
<TD><I>n/a {{ is_favorite }}</I></TD>
{% endfor %}
</TR></TABLE>
{% else %}
<P><I>There are no albums in the database.</I></P>
{% endif %}
{% endblock %}
M2M relationships are brand-new tables created. They get an unique name, and have two foreign keys. A composite key is created so the direct and the related model combinations are unique.
When you do:
class Topping(models.Model):
name = ...
class Pizza(models.Model):
name = ...
toppings = models.ManyToManyField(Topping, related_name="pizzas")
#not including a related_name will generate a "pizza_set" related name.
A new table appears describing the relationship, with an internal name. This table has a pizza_id and a topping_id foreign key, and a composite unique key including both fields. You cannot, and should not, predict the name of such table.
On the other side, if you want to access the relationship and, possible, declare more fields, you can:
class Topping(models.Model):
name = ...
class Pizza(models.Model):
name = ...
toppings = models.ManyToManyField(Topping, related_name="pizzas", through="PizzaAndTopping")
#not including a related_name will generate a "pizza_set" related name.
class PizzaAndTopping(models.Model):
more_data = models.TextField()
pizza = models.ForeignKey(Pizza, null=False)
topping = models.ForeignKey(Topping, null=False)
class Meta:
unique_together = (('pizza','topping'),)
Notice how I added a through parameter. Now you have control of the middle-table BUT you cannot add or delete models from the relationship. This means, with this approach you cannot:
Pizza.objects.get(pk=1).toppings.append(Topping.objects.get(pk=2))
Nor you can remove, nor you can do these operations in the Topping side of the life.
If you want to add or delete toppin-pizza relationships, you must do it directly in the PizzaAndTopping relationship.
If you want to know whether the current user has marked any song as their favorite, you should prefetch the relationship. In Django 1.7, you can prefetch a M2M related field using a custom filter: you can fetch all the albums, and only a query getting the current user, using a Prefetch object. See the official docs about prefetching here.
Antoher solution would involve:
fetching the current user's favorite albums list: ulist = user.album_set.all()
fetching the current page of alboms: _list = Album.objects.all()[0:20]
fetch values of user albums: ulist = ulist.values_list('id', flat=True)
[1, 2, 4, 5, 10, ...] #you'll get a list of ids
when iterating over each album in the page, you test currentAlbum.id in ulist and print a different message (either yes or no).
The many-to-many field is represented in the database in exactly the same way as your original FavouriteSongs model - as a linking table with ForeignKeys to both Song and User. The only benefit of getting rid of FavouriteSongs is that you're now using an automatically-defined through table, rather than a manual one.
I don't understand your example query, since you don't say what model you are actually calling it on, or what self.logged_in_user is. However, you can't use extra like this: you are trying to put Django query syntax there, complete with double-underscore names to traverse relationships, but extra is passed directly to the SQL, and that doesn't know anything about that syntax.
I would not attempt to do this in one query. Instead I would do two queries, one to get all the albums and one to get the user's favourites. get_queryset would just return the full album list, and then you can use get_context_data to get an additional set of objects representing the IDs of the favourites:
favorites = self.logged_in_user.album_set.all().values_list('id', flat=True)
context['favorites'] = set(favorites)
The values_list just gets the IDs of the albums only, since that's all we need, and we then put them into a set to make lookups quicker.
Now, in the template, you can just do:
{% for album in albums %}
...
<td>{% if album.id in favorites %}Yes{% else %}No{% endif %}</td>
{% endfor %}