Django - Divide query into subgroups by attribute - python

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.

Related

How to display the list of videos in two categories 1.'currentUser' videos and 2. 'otherUsers' videos within same class in django

The below code gives "otherUsers" videos
class Test(ListView):
model = VideoUpload
template_name = 'videoTube.html'
The below code gives "currentUser" videos
class Test(ListView):
model = VideoUpload
template_name = 'videoTube.html'
def get_queryset(self):
return VideoUpload.objects.filter(user=self.request.user)
In templates
{% for vid in object_list %}
{% if forloop.counter < 5 %}
{{ vid.video }}
{% endif %}
{% endfor %}
My requirement is to display the list of videos in two categories
1.List of 'currentUser' videos
and
2.List of 'otherUsers' videos
within same class 'Test' which should display on the same html page
you need to define method
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["other_video_list"] = VideoUpload.objects.exclude(user=self.request.user)
return context
and then in your template you can use
{% for other_vid in other_video_list %}
...
{% endfor %}

How to iterate through multidimensional list/dict in a template

I try to rebuild this example:
https://blog.roseman.org.uk/2010/01/11/django-patterns-part-2-efficient-reverse-lookups/
I have a model "Product" and a model "Order". Order has a foreignkey to "product". So for 1 Product I have N Orders
In my template I have to display a lot of information so I would like to avoid to do "for order in Product.order_set.all()" in my template
In my template, if I write :
{{ object_list.1.related_items }}
everything is fine and I get what I want
but if I write:
{% for i in object_list %}
{{ object_list.i.related_items }}
{% endfor %}
I don't get a result.
Can somebody tell me how I solve this problem?
My object_list is nearly the same as in the above example:
products = Product.objects.all()
i = 0
qs = Product.objects.all()
obj_dict = dict([(obj.id, obj) for obj in qs])
objects = Order.objects.filter(producttyp__in=qs)
relation_dict = {}
for obj in objects:
relation_dict.setdefault(obj.producttyp_id, []).append(obj)
for id, related_items in relation_dict.items():
obj_dict[id].related_items = related_items
def get(self,request,*args,**kwargs):
context = {'object_list':self.obj_dict}
return render(request,self.template_name,context)
the only change i did is from
obj_dict[id]._related_items to obj_dict[id].related_items because of the not allowed underscore?!
How do I print the list in my template like:
- Product A
- Order 1
- Order 2
- Order 5
- Product B
- Order 3
- Order 6
best regards
That is logical, since here Django interprets i not as the variable, but as the an identifier, so it aims to access object_list.i, or object_list['i'], not object_list.1 for example.
You however do not need i here, you can just access the related_items of the object, like:
{% for object in object_list %}
{{ object.related_items }}
{% endfor %}
If related_items is, as the name suggests, a collection as well, we can iterate over these items as well:
{% for object in object_list %}
{% for subitem in object.related_items %}
{{ subitem }}
{% endfor %}
{% endfor %}
for a dictionary, we can access the .values, like:
{% for object in object_dict.values %}
{{ object.related_items }}
{% endfor %}
EDIT: as for the specific case of the listview. You can use .prefetch_related to fetch all the relations with one extra query:
class MyListView(ListView):
queryset = Product.objects.prefetch_related('order_set')
template = 'my_template.html'
In the template you can then render this like:
<ul>
{% for product in object_list %}
<li>{{ product }}</li>
<ul>
{% for order in product.order_set %}
<li>{{ order }}</li>
{% endfor %}
</ul>
{% endfor %}
</ul>

Django - trouble with separating objects by user and date

So I have these models:
excercises_choices = (('Bench Press', 'Bench press'),('Overhead Press', 'Overhead Press'), ('Squat', 'Squat'),
('Deadlift', 'Deadlift'))
unit_choices = (('kg','kg'), ('lbs', 'lbs'))
class Lifts(models.Model):
user = models.ForeignKey('auth.User', null=True)
excercises = models.CharField(max_length=200, choices=excercises_choices)
sets = models.IntegerField(null=True, blank=True)
reps = models.IntegerField(null=True, blank=True)
weight = models.FloatField()
unit = models.CharField(max_length=3, choices=unit_choices)
created_date = models.ForeignKey('Dates')
amrap_set = models.BooleanField(default=False)
amrap_rep = models.IntegerField(null=True, blank=True)
def __str__(self):
return self.excercises
class Dates(models.Model):
created_date = models.DateField(unique=True)
def __str__(self):
return str(self.created_date)
Let's say I have few lifts at different dates for admin and few lifts at different for xx user.
I want multiple lifts matching one date that's why I've made foreign key. (eg. 3 lifts to 2016-10-10 and 2 lifts to 2016-10-11).
Here is a view for showing it:
#login_required
def entries(request):
date = Dates.objects.all().order_by('-created_date')
lifts_by_user = Lifts.objects.filter(user=request.user)
return render(request, 'lift/entries.html', {'date': date,
'lifts_by_user': lifts_by_user})
And template:
{% extends 'lift/base.html' %}
{% block content %}
{{ user }}
{% if user.is_authenticated %}
{% for date in date %}
<p><strong>{{ date }}</strong>
{% for i in date.lifts_set.all %}
{{ i }}
{% endfor %}
add new lift</p>
{% endfor %}
{% endif %}
<p>
Delete lifts or dates
</p>
{% endblock %}
The problem is that I dont know how to separate it by dates AND by user.
This is how it looks like How do i keep this pattern date - lifts_to_that_date but for separate users? I dont want to see admin's entries while I am on test user
Have a look at the regroup template tag, it does exactly what you need.
You can do something like this in your view:
#login_required
def entries(request):
lifts_by_user = (Lifts.objects.filter(user=request.user)
.order_by('-created_date__created_date'))
return render(
request,
'lift/entries.html',
{'lifts_by_user': lifts_by_user}
)
And replace the for date in dates loop in your template with something like:
{% regroup lifts_by_user by created_date.created_date as lifts %}
<ul>
{% for day in lifts %}
<li>Date: {{ day.grouper }}
<ul>
{% for lift in day.list %}
<li>{{ lift }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
I've used a ul here so that it's easier to compare to the example in the docs, but obviously you can change the markup to whatever you need. It's important to know that regroup doesn't order its input, so you need to order by created_date in your view.
If you're using Django's dev version you can use this instead:
{% regroup lifts_by_user by created_date.created_date as lift_list %}
<ul>
{% for day, lifts in lift_list %}
<li>Date: {{ day }}
<ul>
{% for lift in lifts %}
<li>{{ lift }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
Which I think is a little clearer.
As an aside, none of this relies on having dates stored as a foreign key, but that's up to you.
Questions from comments:
order_by('-created_date__created_date') is joining Lifts to Dates through the Lifts.created_date foreign key and ordering by the Dates.created_date field. Have a look at https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships for details.
for day, lifts in lift_list is using tuple unpacking.
As a quick example:
t = (1, 2, 3)
# first, second, third will have values 1, 2, 3 respectively
first, second, third = t
{% regroup lifts_by_user by created_date.created_date as lifts_list %} produces a list of namedtuples (again, only in the dev version, if you're using 1.10 or earlier it's a list of dicts so you can't use this trick) so as you're iterating through lift_list you can unpack the date and list of lifts into separate variables.
If you have a Lift instance called lift, you can get the pk for its date by using lift.created_date_id. Accessing it where you have the date URL in your example template is a little trickier because you have to get a lift out of the regrouped date's list. Something like this:
{% regroup lifts_by_user by created_date.created_date as lifts %}
<ul>
{% for day in lifts %}
<li>Date: {{ day.grouper }}
{# day.list.0 gets the first lift for this day #}
Date PK: {{ day.list.0.created_date_id }}
<ul>
{% for lift in day.list %}
<li>{{ lift }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>

Select reverse Foreign Key in ModelChoicefield

I have an order form where the user can choose one item and select a quantity. The price depends on how much is ordered. For example, each item is $10 if you order <100, but $7 if you order 100-200.
In the template, I want to display the pricing information underneath the form for each choice.
These are my Models:
class Product(models.Model):
name = models.TextField()
class Price(models.Model):
"""
This lets us define rules such as:
When ordering <100 items, the price is $10
When ordering 100-200 items, the price is $7
When ordering 200-300 items, the price is $5
etc
"""
price = models.FloatField()
min_quantity = models.PositiveIntegerField()
max_quantity = models.PositiveIntegerField()
product = models.ForeignKey(Product)
class Order(models.Model):
product = models.ForeignKey(Product, null=False, blank=False, default='')
quantity = models.IntegerField()
I can loop over the form fields and the queryset independently:
{% for choice in form.product.field.queryset %}
<h1>{{choice}} {{choice.price_set.all}}</h1>
{% endfor %}
{% for choice in form.product %}
<h1>{{ choice.tag }} {{ choice.choice_label }}</h1>
{% endfor %}
... but I don't know how to combine the loops to display the prices underneath the form fields.
Essentially, I want to select a reverse foreign key from a ModelChoicefield widget. I either need to loop over both the form fields and the queryset at once or access elements in the queryset from the form element. Ideally, this is what I'd like to do in my template:
{% for choice in form.product %}
<h1>{{ choice.tag }} {{ choice.choice_label }}</h1>
{% for price in choice.price_set.all %}
<h1>{{price}} etc...</h1>
{% endfor %}
{% endfor %}
Surely I'm not the first person with this use case. What's the best way to do this?
Edit: As requested, this is my form and my view. Reviewing this, I suppose I should have mentioned I was using the RadioSelect widget.
Form:
class OrderForm(forms.ModelForm):
class Meta:
exclude = ['date_added']
widgets = {
'mailer': forms.RadioSelect
}
model = Order
View:
def processOrder(request):
if request.method == 'POST':
orderForm = OrderForm(request.POST)
if orderForm.is_valid():
orderObject = orderForm.save()
return render(request, TEMPLATE_PREFIX + "confirm.html", {"orderObject": orderObject})
else:
return render(request, TEMPLATE_PREFIX + "register.html", { "form": orderForm })
else:
return render(request, TEMPLATE_PREFIX + "register.html", { "form": OrderForm()})
For (non)perfectionists with deadlines, this code works, albeit inefficiently.
{% for choice in form.product %}
{% for price_candidate in form.mailer.field.queryset %}
{% if price_candidate.id == choice.choice_value|add:0 %}
<h1>{{ choice.tag }} {{ choice.choice_label }}</h1>
{% for price in price_candidate.price_set.all %}
<h1>{{price}} etc...</h1>
{% endfor %}
{% endif %}
{% endfor %}
{% endfor %}
(The add:0 hack converts choice_value into an int. cf http://zurb.com/forrst/posts/Compare_string_and_integer_in_Django_templates-0Az )

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

Categories