Django: Loop / iterate nested list with multiple sublevels - django

Here are my snippets:
models.py
class Item(models.Model):
name = models.CharField('Name', max_length=50, unique=True)
toplevel = models.BooleanField('Top level item', default=False)
order = models.IntegerField('Order number', blank=True, null=True) #only if top level is true, determines the top level order
class Hierarchy(models.Model):
mainitem = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='mainitem')
subitem = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='subitem')
order = models.IntegerField('Order number', default=0) #determines the order of the subitems under the mainitem
views.py
def items_view(request, *args, **kwargs):
items = Item.objects.all()
toplevelitems = Item.objects.filter(toplevel=True)
sublevelitems = Item.objects.filter(toplevel=False)
context = {
'items':items,
'toplevelitems':toplevelitems,
'sublevelitems':sublevelitems,
}
return render(request, "itemlist.html", context)
itemlist.html
{% if items %}
<ul class="list-group list-group-flush">
{% for toplevelitem in toplevelitems %}
<li class="list-group-item">{{ toplevelitem.order }} {{ toplevelitem.name }}</li>
{% for sublevelitem in sublevelitems %}
<ul class="list-group list-group-flush">
<li class="list-group-item">{{ sublevelitem.name }}</li>
</ul>
{% endfor %}
{% endfor %}
</ul>
{% else %}
<p>No created items</p>
{% endif %}
So what I am trying to built with these is a hierarchy of items that will be itereted for the client in a nested list. What I cannot figure out is how to loop through all the levels of the hierarchy and place them under the right upper level. Here is what I am going for:
Item 1 (top level item)
Item 1.1 (sub level item)
Item 1.1.1 (sub sub level item)
Item 1.1.1.1 (sub sub level item)
...
item 1.....n (n sub level item)
Item 1.2 (sub level item)
Item 1.2.1 (sub sub level item)
Item 1.2.1.1 (sub sub level item)
...
item 1.....n (n sub level item)
Item 2 (top level item)
Item 2.1 (sub level item)
Item 2.1.1 (sub sub level item)
Item 2.1.1.1 (sub sub level item)
and so on...
Problem with this is the nested list can contionue up to anything and I don't know how to loop it to the last bit.
I know that the loop in html isn't doing anything like this, it only list all the subitems not considering where it belongs in the hierarchy. What would I need to change, probably in my queries and html to do something like I want?

Related

Django PostgreSQL – Efficiently fetch recursive category structure

I have a model which looks like this:
class Category(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField()
parent = models.ForeignKey(
'categories.Category',
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='categories'
)
basically, in the parent field, it references itself. If a parent is set to None, it's the root category.
I use it to build a hierarchy of categories.
What would be the most efficient way to:
fetch all the objects through the hierarchy
display them in a template?
For some reason, select_related does not seem to lead to performance improvements here.
I also found this: How to recursively query in django efficiently?
But had a really hard time applying it to my example, because I still don't really understand what's going on. This was my result:
WITH RECURSIVE hierarchy(slug, parent_id) AS (
SELECT slug, parent_id
FROM categories_category
WHERE parent_id = '18000'
UNION ALL
SELECT sm.slug, sm.parent_id
FROM categories_category AS sm, hierarchy AS h
WHERE sm.parent_id = h.slug
)
SELECT * FROM hierarchy
Would appreciate any help.
Thanks!
One possible solution can be using https://django-mptt.readthedocs.io/en/latest/overview.html#what-is-django-mptt
MPTT is a technique for storing hierarchical data in a database. The
aim is to make retrieval operations very efficient.
The trade-off for this efficiency is that performing inserts and moving items around the tree is more involved, as there’s some extra work required to keep the tree structure in a good state at all times.
from django.db import models
from mptt.models import MPTTModel, TreeForeignKey
class Category(MPTTModel):
name = models.CharField(max_length=50)
slug = models.SlugField()
parent = TreeForeignKey(
'self',
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='children'
)
class MPTTMeta:
order_insertion_by = ['name']
You can use the django-mptt template tag as this:
{% load mptt_tags %}
<ul>
{% recursetree categories %}
<li>
{{ node.name }}
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
There is a tutorial and more information int library docs.
I had the same problem and ended up creating the following function that hits the database once, then sorts out the heirarchy and returns a dict:
def get_category_tree():
categories = Category.objects.order_by('name')
itemtree = {}
# Add 'children' attribute to each category; populate dict
for category in categories:
category.children = {}
itemtree[category.pk] = category
# Add categories to 'children'
for key,value in itemtree.items():
if value.parent_id:
itemtree[value.parent_id].children[key] = value
# Return top-level items
return {k:v for k,v in itemtree.items() if not v.parent_id}
Each value of the returned dict is a top-level Category object which has a children attribute.
You can render it in the template by looping through the dict values. The following example will handle three levels of heirarchy:
<ul>
{% for level1 in category_tree.values %}
<li>
{{ level1.name }}
{% if level1.children %}
<ul>
{for level2 in level1.children.values %}
<li>{{ level2.name }}
{% if level2.children %}
<ul>
{for level3 in level2.children.values %}
<li>{{ level3.name }}</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
If you need to render many levels of the heirarchy, you could consider using template recursion. Have a read of the following question and answers to determine if that might be suitable: Represent a tree of objects in Django template

how to use a forloop such that the if the "IF" statement in the loop is satisfied the loop ends in Django Templates

I have 2 Django models Review and Item that I am working with. I want to see if the user has already reviewed the item. If yes he sees the review score. if no he sees the button to review the item
I have the below Review model
class Review (models.Model):
review_from = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='review_from')
review_for = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='review_for')
item = models.ForeignKey(OrderItem, related_name='items')
Defining the variables in the view context (pseudocode)
admin = User.objects.get(username="admin")
admins_reviews = Review.objects.filter(review_from__username = "admin")
Below is my template
{% for item in buyers_items %}
{% for review in buyers_review%}
{% if review.item.id == item.id %}
<button class="text-success">Your rating<br/><b>{{review.ratings}}/10</b></button>
{% else %}
<a href="{% url ... %}">
<button>Leave Review</button>
</a>
{% endif %}
{% endfor %}
{% endfor %}
If I do this I get a below error
How can I overcome this problem.
View
from django import template
register = template.Library()
class OrderHistory(LoginRequiredMixin, ListView):
model = Order
template_name = 'order/order_list.html'
def get_context_data(self, **kwargs):
context = super(OrderHistory, self).get_context_data()
context['order_details'] = Order.objects.filter(emailAddress=self.request.user.email)
context['order_items'] = OrderItem.objects.filter(order__emailAddress=self.request.user.email)
context['buyers_review'] = Review.objects.filter(review_from=self.request.user)
print(context['buyers_review'])
return context
Custom Tag
#register.filter()
def review_bought_items(order_items, buyers_review):
return buyers_review.filter(item__in=order_items).exists()
Based on what I see in your templates, you could do it simpler with a tag filter or in your view side. Let's go with a custom tag:
#register.filter
def review_bought_items(buyers_items,buyers_review):
return buyers_review.filter(item__in=buyers_items).exists()
Now in the templates you could do
<!-- load the tag -->
{% load file %}
{% if buyers_items|review_bought_items:buyers_review %}
<button class="text-success">Your rating<br/><b>{{review.ratings}}/10</b></button>
{% else %}
Leave Review
{% endif %}
The issue is that you are iterating over all buyers_reviews. In this particular case, you have 2 buyer reviews, one for the current item and one for a different one.
First iteration will evaluate to False the first condition and it will display all the Leave Review button and the 2nd iteration will evaluate it to True and display the "Your rating" block.
If you don't want to move all the logic on the backend, maybe make us of a template tag in order to filter the reviews based on item.id

Django remove duplicates in queryset and access their list of foreign key

Here is my model:
class Item(models.Model):
title = models.TextField()
class Storage(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
rating = models.IntegerField(blank=True, null=True)
review = models.CharField(max_length=1500, blank=True, null=True)
class SocialProfile(models.Model):
user = models.OneToOneField(User, unique=True)
follows = models.ManyToManyField('self', related_name='followed_by', symmetrical=False)
Say there are 4 users A,B,C,D. User A follows only B and C.
I'm trying to do a view that when log in as User A. Show a list of items that B and C has (duplicates show only one time). And under each item, show their summary and ratings.
I have something here: (user = User A here)
//get a list of users that User A follows
followings = user.socialprofile.follows.all()
//get B and C's saved item, eliminate the duplicates
context['following_saved_article'] = Item.objects.filter(storage__user__in=followings.values('user')).distinct()
//get a flat list of all users A follows
context['followings'] = list(followings.values_list('user', flat=True).order_by('user'))
Then in the template:
{% for item in following_saved_item %}
{{ item.title }}
{% for x in item.storage_set.all %}
{% if x.user_id in followings %}
{{ x.review }} {{ x.rating }}
{% endif %}
{% endfor %}
{% endfor %}
This seems too redundant, and I will have a pretty hard time if I want to sort reviews by user rating.
Is there any way to generate a list of distinct Item instance, fetch only the storage_set based on following, sort by rating or other field in storage, and pass that to view? Thanks
I think a custom Prefetch object will do the trick
followings = user.socialprofile.follows.all()
items = Item.objects.filter(
storage__user__in=followings.values('user')
).distinct().prefetch_related(
models.Prefetch('storage_set',
queryset=Storage.objects.filter(
user__in=followings.values('user')
),
to_attr='storage_from_followings'
)
)
#in the template
{% for item in items %}
{{ item.title }}
{% for x in item.storage_from_followings %}
{{ x.review }} {{ x.rating }}
{% endfor %}
{% endfor %}

Django manytomany field filter list

I am printing a list of beers matching some filters, and the bars where each is on tap. These are in a manytomany relationship. I need to filter this list of bars to only show those in a given state.
I can achieve this using if statements in the template, but then am unable to format the list to use commas with an 'and' before the final item (like https://stackoverflow.com/a/3649002/6180992), as I do not know the length of the list.
I have thought of three ways this might be possible, but cannot get any to work:
filtering the bars related field as well as the beers in the views
assembling the list in the template before looping through again to print it
filtering the bars related field in the template
Here are the relevant sections of code:
models.py
class Bar(models.Model):
bar = models.CharField(max_length=200, default='FinshnPig')
state = models.CharField(max_length=200,default='NY')
def __str__(self):
return self.bar
class Meta:
ordering = ('bar','region')
class Tap(models.Model):
bar = models.ManyToManyField(Bar,default='FinshnPig')
brewery = models.CharField(max_length=200)
beer = models.CharField(max_length=200)
state = models.CharField(max_length=200, default='NY')
def __str__(self):
return self.beer
views.py
f = TapFilter(request.GET, queryset=Tap.objects.filter(state="VIC"))
template:
{% for tap in filter %}
<li>
<b>{{ tap.beer }}</b>
<em>{{ tap.brewery }}</em>
#{% for bar in tap.bar.all %}{% if bar.state == "VIC" %}{{ bar.bar }}</b>{% endif %}{% include "taplists/comma.html" %}{% endfor %}
</li>
{% endfor %}
You can use prefetch for querying only the related bars before sending it to template, like so:
prefetch = Prefetch(
'bar',
queryset=Bar.objects.filter(state=CHOSEN_STATE),
to_attr='selected_states'
)
filter = Tap.objects.filter(state=CHOSEN_STATE).prefetch_related(prefetch)
Now inside your template use the custom attribute you assigned:
{% for tap in filter %}
# Now selected_bars only contains the bars in the state you want
{% for bar in tap.selected_bars.all %}
...
{% endfor %}
{% endfor %}
Additional info on Django docs for prefetch_related
Note: Using prefetch for ManyToMany relations will also increase the performance, as there won't be as many database lookups.

How to list items by "type" in Django view

First of all I am very new to Django and this is a simple question. I apologize but I can't seem to be able to word a search correctly to find an answer. Any help you can provide would be greatly appreciated.
fyi: Django version is 1.3.7
I have a simple inventory app in the works and am creating a view that would list items by device type (eg: Tablet), and model type (eg: iPad Air or iPad 2). I want to be able to list all instances of a device type and have the results sorted by model. For example my page would look something like this:
TABLETS:
iPad's Airs:
- list of ALL iPads Airs
iPad 2's:
- list of ALL iPad 2's.
Unfortunately my page displays a single instance of the model type in each list and creates a new list for each device, eg:
TABLETS:
iPad's Airs:
- only single item listed
iPad Air's:
- only single item listed
etc...
iPad 2's:
- only single item listed
iPad 2's:
- only single item listed
etc...
Here are the entries in my models.py (edited down to just the important bits):
class DeviceType(models.Model):
device_type = models.CharField(max_length=25)
def __unicode__(self):
return self.device_type
class DeviceModelType(models.Model):
model_type = models.CharField(max_length=25)
def __unicode__(self):
return self.model_type
class Device(models.Model):
...
device_type = models.ForeignKey(DeviceType)
device_model = models.ForeignKey(DeviceModelType)
name_of_device = models.CharField(max_length=25)
...
def __unicode__(self):
return u'%s, %s, %s' % (self.name_of_device, self.manufacturer, self.device_model)
here is my view:
def check_out(request, device_id):
available_device_list = Device.objects.all().order_by('id')
return render_to_response('inventory/list.html', {'available_device_list': available_device_list, })
and my template:
{% extends "base.html" %}
{% block title %}Inventory Test{% endblock title %}
{% block head %}Inventory Test{% endblock head %}
{% block content %}
<p><h3>Inventory:</h3></p>
{% for Device in available_device_list %}
{{ Device.device_model }}'s:
<ul>
<li>
<a href="http://127.0.0.1:8000/inventory/device_checkout.html".{{ Device.device_model }}<strong>{{ Device.name_of_device }}</strong></a>
</li>
</ul>
{% endfor %}
{% endblock content %}
{% block footer %}
<p>
<hr/><br/>
admin</p>
{% endblock footer %}