how to do a loop within a loop - python

Im trying to create a section in my application which contains a loop of catagories, and then the forms within that category. For example it should display like this:
Category 1
form 1
form 2
Category 2
form 3
But what im actualy getting is:
Category 1
form 1
form 2
form 3
Category 2
form 1
form 2
form 3
How can i fix this?
My view is:
def homepage (request):
Categories = Category.objects.all()
Forms = Form.objects.all()
output = {
'Category_Name': Categories,
'Form_Title': Forms,
}
return render_to_response('forms/home.html', RequestContext(request, output))
And my HTML is:
<ul>{% for c in Category_Name %}<li>{{ c.Name }}<ul>{% for c in Form_Title %}
<li>{{ c.Title }}</li>{% endfor %}</ul></li>{% endfor %}</ul>

if your forms are associated to a category via a foreignkey
class Form(models.Model):
...
category = models.ForeignKey(Category)
you could do something like this:
<ul>
{% for c in Category_Name %}
<li>{{ c.Name }}
<ul>
{% for form in c.form_set.all %}
<li>{{ form.Title }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
take a look at the foreignkey documentation.
also note its not a good idea to use the same variable (c) in the outer and the inner loop,even if it works like in your example
and in python variable names are usually written lowercase
from django.shortcuts import render
def homepage (request):
categories = Category.objects.all()
return render(request, 'forms/home.html',
{'categories': categories})

There is a regroup template tag for this use case: https://docs.djangoproject.com/en/1.4/ref/templates/builtins/?from=olddocs#regroup
An additional advantage to use regroup is, that there is only ONE sql call involved. This might be an issue if you have many categories.

Related

DJANGO: Sorting sets of sets

I have this models (simplified):
#models.py
class Expression(models.Model):
text = models.CharField(max_length=254)
class Country(models.Model):
name = models.CharField(max_length=100)
class Definition(models.Model):
expression = models.ForeignKey(Expression)
country = models.ForeignKey(Country)
text = models.CharField(max_length=254)
class Vote(models.Model):
definition = models.ForeignKey(Definition)
And this view
#views.py
def index(request):
expressions = Expression.objects.all()
return render(request, 'expression_index.html', { 'expressions':expressions)
So it will show the last 10 created expressions.
Then in the template I have this:
#index.html
{% for expression in expressions %}
{{ expression }}
{% for definition in expression.definition_set.all %}
<ul>
<li>{{ definition }}</li>
</ul>
{% endfor %}
{% endfor %}
Every definition has several votes.
Every vote is a single row so we can do:
definition.votes_set.count()
How can I achieve to display them like this:
The top definition of every country alphabetically. Each country appears only with one definition.
Lets say Germany has two definitions for expression "A" and Denmark has three definitions for the same expression it will show only two definitions: the one with the most votes.
I hope I'm making sense.
Thanks
I think something like this should work (untested)
from django.db.models import Count
{{ expression.definition_set.annotate(Count('votes_set')) }}
{% for definition in expression.definition_set.order_by('country','votes_set__count') %}
This queryset will sort alphabetically by country, then each country from it's top voted definition to least.
expression = Expression.objects.get(text__exact="A")
definitions = Definition.objects.filter(expression=expression).annotate(num_votes=Count('vote')).order_by("country__name", "-num_votes")
If i try to keep only the top definition of each country as you wanted, and set .distinct("country") at the end of the queryset, it will throw this error:
NotImplementedError at annotate() + distinct(fields) is not implemented.
So another solution would be:
import operator
expression = Expression.objects.get(text__exact="A")
# Get top definitions sorted by num of votes and convert the values to list.
top_definitions = list(Definition.objects.filter(expression=expression).annotate(num_votes=Count('vote')).order_by("num_votes").values("id", "country", "country__name", "expression", "text", "num_votes"))
# Remove dublicate countries and leave the only the top voted definition.
definitions = list({v['country']: v for v in top_definitions}.values())
# Sort alphabetically by country.
definitions.sort(key=operator.itemgetter('country__name'))
return render(request, 'expression_index.html', {'definitions': definitions, 'expression': expression})
template:
<h1>{{ expression.text }}</h1>
<ul>
{% for definition in definitions %}
<li>{{ definition.country__name }} - {{ definition.text }} - {{ definition.num_votes }}</li>
{% endfor %}
</ul>

How to display multiple ForeignKey filtered items in a single view in django?

I'm trying to understand the best way to display ForeignKey filtered data in a Django model.
I have three models reduced to this:
// models.py
class Publisher(models.Model)
def publisher_name = models.TextField()
def publisher_slug = models.SlugField()
def founded_year = models.IntegerField()
class Album(models.Model)
def album_name = models.TextField()
def publisher = models.ForeignKey('Publisher', related_name='albums')
class Song(models.Model)
def song_name = models.TextField()
def album = models.ForeignKey('Album', related_name='songs')
def published_year = models.IntegerField()
I have a URL that is composed of: /<publisher>/<published_year>/
The view I'm having trouble composing is supposed to be details like this:
Title of: Publisher.publisher_name
List of All Albums by the publisher: List of All songs from that album published the same year as the publisher__published_year: List of All songs from that album published as the url
The way, I've tried to do this that works right now is similar to this:
// views.py
class SongYearView(TemplateView):
def get_context_data(self, **kwargs):
context = super(SongYearView, self).get_context_data(**kwargs)
context['publisher'] = Publisher.objects.get(slug=kwargs['publisher_slug']
album_list=[]
for album in context['publisher'].albums.all():
single_album = dict()
single_album['album'] = album
single_album['publisher_year_song'] = album.songs.filter(published_year=context['publisher'].published_year)
single_album['filtered_year_song'] = album.songs.filter(published_year=kwargs['published_year']
album_list.append(single_album)
context['albums'] = album_list
return context
Then in the template I'm doing (with stripped out formatting)
// template.html
{{ publisher.name }}
{% for album in albums %}
{{ album.album.album_name }}
{% for song in album.publisher_year_song %}
{{ song.song_name }}
{% endfor %}
{% for song in album.filtered_year_song %}
{{ song.song_name }}
{% endfor %}
{% endfor %}
While this does work, it's not pretty and I'm pretty sure there are better ways of doing this.
This is an odd example, but just a basic example of my more detailed models. The way to think about it is Publisher -> Album -> Song or A -> B -> C. And I'm trying to get a view of all B items, that are only linked with a specific A item and then get two sets of C items for each B item, where one set is filtered on an A property and the other set is filtered on a passed argument from a URL.
I tried to get a custom model.Manager to help get this constructed, but didn't have much luck.
You could do add a custom template filter of_year:
#register.filter
def of_year(songs, year):
return songs.filter(published_year=year)
And change your template to
// template.html
{{ publisher.name }}
{% for album in publisher.albums %}
{{ album.album.album_name }}
{% for song in album.songs|of_year:publisher.founded_year %}
{{ song.song_name }}
{% endfor %}
{% for song in album.songs|of_year:filtered_year %}
{{ song.song_name }}
{% endfor %}
{% endfor %}
And clean your view:
// views.py
class SongYearView(TemplateView):
def get_context_data(self, **kwargs):
context = super(SongYearView, self).get_context_data(**kwargs)
context['publisher'] = Publisher.objects.get(slug=kwargs['publisher_slug'])
context['filtered_year'] = kwargs['published_year']
return context
Edit: rename the template filter

Check a List With If Statement in a Loop Using Python/Django

I have an Article model which I fetch from when a user successfully logs in:
articles = Article.objects.all()
I also have Likes model which has user_id and article_id to record articles users have liked.
articles_liked = Likes.objects.values_list('article_id', flat = True)
users_who_liked = Likes.objects.values_list('user_id', flat = True)
The article_id gives a list of article users have liked while users_who_liked gives a list of users who liked those articles as shown below:
articles_liked = [4, 3, 5, 6, 10]
users_who_liked = [1, 13, 15, 16, 110]
What I want to achieve is to be able to tell if a particular post is already liked by the logged in user in my template.
{% for article in articles %}
<p>{{article.content}}</p>
{% if this_user_id in users_who_liked and article.id in articles_liked %}
<p class = 'likes'> 2 </p>
{% else %}
<p class = 'likes already_liked'> 2 </p>
{% endif %}
{% endfor %}
Let's assume this_user_id = 1 and the articles he/she likes is 4, the if statement returns True for all the articles and the else block never get to be executed.
How can I do this the right way?
Your variables articles_liked and users_who_liked are not correlated to each other. They always contain all liked articles and all users who liked, but between them there is no relation in your template context which you can rely on.
Therefore your if statement will always return True for every user who liked any article which has been liked by at least one user.
Assuming from your question that Like has a foreign key (Django docs) to User and a foreign key to Article you could just add a filter to your articles_liked assignment to only retrieve likes from the current user:
articles_liked = Likes.objects.filter(user_id=this_user_id).values_list('article_id', flat = True)
Then in your template you can simply check if the current article is in this list:
{% for article in articles %}
<p>{{article.content}}</p>
{% if article.id in articles_liked %}
<p class = 'likes'> 2 </p>
{% else %}
<p class = 'likes already_liked'> 2 </p>
{% endif %}
{% endfor %}
As your Likes object has a reference to both Users and Articles, why not exploit this in your view? That way you control the logic in your view and template just becomes a simple if statement:
def some_view(request):
articles = Article.objects.all()
user = request.user
article_list = []
for a in articles:
# Create a tuple, with a flag if the user has liked this article or not.
article_status = (Likes.objects.filter(article_id=a.pk, user_id=user.pk).exists(), a,)
article_list.append(article_status)
return render(request, 'template.html', {'articles': article_list})
Now, in your template you have the following:
{% for liked,article in articles %}
{% if liked %}
<strong>You already liked this article!</strong>
{% endif %}
{{ article }}
{% endfor %}

Django - Divide query into subgroups by attribute

I have a model that looks like this:
class ListEntry(models.Model):
STATUS_CHOICES = (
('PL', _('Playing')),
('CO', _('Completed')),
('OH', _('On-hold')),
('DR', _('Dropped')),
('WA', _('Want to play')),
)
user = models.ForeignKey(User)
game = models.ForeignKey(Game)
status = models.CharField(_('Status'), max_length=2,
choices=STATUS_CHOICES)
In my views I'm filtering the entries by user like this:
class GameListByUserView(ListView):
model = ListEntry
template_name = 'game_list_by_user.html'
def get_queryset(self):
self.user_profile = get_object_or_404(User, username=self.kwargs['slug'])
return ListEntry.objects.filter(user=self.user_profile)
def get_context_data(self, **kwargs):
context = super(GameListByUserView, self).get_context_data(**kwargs)
context['user_profile'] = self.user_profile
return context
What I'm trying to do now is split this query (ListEntry.objects.filter(user=self.user_profile)) into subgroups depending on the status attribute, so the rendered template looks like:
UserFoo's list
=========================
Playing
Game 1
Game 2
Game 3
Completed
Game 4
Game 5
Game 6
I know I can do something like:
q = ListEntry.objects.filter(user=self.user_profile)
games_dict = {}
games_dict['playing'] = q.filter(status='PL')
games_dict['completed'] = q.filter(status='CO')
And so on, and iterate over the keys in the template. Or (in the template):
{% for s in status_choices %}
{% for entry in object_list %}
{% if entry.status == s %}
Render things
{% endif %}
{% endfor %}
{% endfor %}
But isn't there a better, optimized way to do this without hitting the database every time I get a subquery by status, and without iterating over the objects list multiple times?
You are looking for the regroup filter
{% regroup object_list by status as games_list %}
<ul>
{% for game in games_list %}
<li>{{ game.grouper }}
<ul>
{% for item in game.list %}
<li>{{ item}}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
You might have to customize the way the elements render, but I will let you figure that out yourself.

Django cannot access all objects in a model

My view looks like this:
class PageView(DetailView):
queryset = Page.objects.all()
template_name = 'page.html'
def get_context_data(self, **kwargs):
context = super(PageView, self).get_context_data(**kwargs)
context['category'] = Category.objects.all()
context['categoryitem'] = CategoryItem.objects.all()
return context
When in template I try to execute the given context variables like so {{ category }} it prints out [<Category: Something not so interesting>] aka model name + it's title, I presume the title gets printed out because I've set __unicode__(self): return self.title in the model.py, but I cannot access any other fields from the given object. category.id is blank as so is everything else. How can I access those?
Your code is:
context['category'] = Category.objects.all()
So it should be:
context['categories'] = Category.objects.all()
And in your template:
{% for category in categories %}
{{ category.name }}
{% endfor %}
The output you got in your test makes sense:
[<Category: Something not so interesting>]
it's an array with only one entry, this entry is an object of the class Category, and the string representation of it is "Something not ..."
You need to iterate over the category, since it's queryset. E.g. in your template, you can do
<ul>
{% for c in category %}
<li> {{ c }} </li>
{% endfor %}
</ul>
category in template is queryset ( list of objects) not one single object. You need to iterate over it as
{%for c in category %}
{{c.id}} : {{ c.other_attribute }}
{%endfor%}

Categories