Sort subchild into child into parent django - django

This is mainly working (thanks to a lot of help on a previous question. Sort children into parent in django template
Now I would like to sort sub-children objects into the children objects and then sort the children into their parent. Specifically, I want to sort schools into cities into their respective state. Then, I want the schools to display into the right city in its correct state. What I am looking for would like like this:
State
--- City
-----School
-----School
-----School
--- City
-----School
-----School
-----School
--- City
-----School
-----School
-----School
Right now, I have the cities sorting into the state. However, I cannot figure out to make the schools sort into the city. I am reading up on filters in the documentation. I think the answer lies there. However, I when try to filter the schools into the city, no objects come back.
Is there a way to retrieve the 'school_list' as a child of 'city_list' while still keeping the 'city_list a child of the states ? I would love to learn how.
Edit: Added the School model. Sorting the schools by name inside a city would be ideal.
Any and all help would be appreciated.
Edit: dkarchmer's solution worked perfectly. Thanks again. Hopefully, this will be useful to someone else in the future.
models.py
class State(models.Model):
state_name = models.CharField(max_length=20, default='')
state_slug = models.SlugField()
state_image = models.ForeignKey(Image, null=True)
state_summary = models.TextField(null=True)
def __str__(self):
return self.state_slug
class City(models.Model):
city_name = models.CharField(max_length=55, default='')
city_slug = models.SlugField()
state_image = models.ForeignKey(Image, null=True)
school_image = models.ForeignKey(Image, null=True, related_name='+')
state = models.ForeignKey(State, null=True)
def __str__(self):
return self.city_slug
def def sorted_schools(self):
return self.school_set.all().order_by('school_name')
class School(models.Model):
school_name = models.CharField(max_length=55, default='')
school_slug = models.SlugField()
city = models.ForeignKey(City, null=True)
def __str__(self):
return self.school_slug
views.py
class CityInStateView(ListView):
model = City
template = 'template.html'
slug_field = 'state_slug'
context_object_name = 'city_in_state_list'
def get_context_data(self, **kwargs):
context = super(CityInStateView, self).get_context_data(**kwargs)
context['state'] = self.object
context['city_list'] = self.object.city_set.all().order_by('city_name')
return context
urls.py
urlpatterns = [
url(r'^$', SchoolIndexView.as_view(), name='school_index'),
url(r'^(?P<state_slug>[\w-]+)/$', CityInStateView.as_view(), name='state_index'),
]
template.html
{% block main_content %}
<div class="row body">
<div class="main_content">
<div class="row">
<h1>{{ state.state_name }}</h1>
{% for city in city_list %}
<h2>{{ city.city_name }}</h2>
{% for school in city.sorted_schools %}
<h3>{{ school.school_name }} </h3>
{% endfor %}
{% endfor %}
</div>
</div>
</div>
{% endblock %}
This has been kicking my butt for a week. Please explain it to me like I a m five. I appreciate all of your help in advance.

The easiest is to just add a method to the City model to return Schools:
class City(models.Model):
...
def sorted_schools(self):
return self.school_set.all().order_by('school_name')
And then use that directly from the template:
<div class="row body">
<div class="main_content">
<div class="row">
<h1>{{ state.state_name }}</h1>
{% for city in city_list %}
<h2>{{ city.city_name }}</h2>
{% for school in city.sorted_schools %}
<h3>{{ school.school_name }} </h3>
{% endfor
{% endfor %}
</div>
</div>
</div>
If you don't want to add that method to the Model, the second alternative is to use a templatetag to basically do the same.

Related

Object_list doesn't show the correct template

I have a Django project where users can ask questions and receiving answers. I have three models: Question, Answer and Comment. I don't know why comment template doesn't show the correct data, I dont't hnow where to find the comment data either object.comment, object.comment_set.all or anything else.
I had the same problem with fetching Answer data, but I successfully solved it by using '{% for answer in object.answer_set.all %}', but the same method doesn't apply in comment. I noticed that I don't understand where is all the information stucks to retrieve.
I'm relatively new to Django, so I'd be grateful to get all answers with description, why am getting this to avoid this in the fitire projects.
models.py
class Question(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=255, unique=True, db_index=True, verbose_name="URL")
detail = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('detail', kwargs={'slug': self.slug})
class Answer(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
detail = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.detail
class Comment(models.Model):
answer = models.ForeignKey(Answer, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='comment_user')
detail = models.TextField(default='')
date_posted = models.DateTimeField(default=timezone.now)
views.py
class AnswerView(ListView):
model = Answer
template_name = 'forum/detail.html'
class CommentView(ListView):
model = Comment
template_name = 'forum/detail.html'
detail.html
<div class="col-11">
{% for answer in object.answer_set.all %}
<p>{{ answer.detail }}</p>
<p>{{ answer.user.username }}
<span>5 комментариев</span></p>
{% endfor %}
</div>
<!-- Comment Section Starts-->
<h3 class="my-4">Комментарии:</h3>
{% for comment in comment.answer.all %}
<div class="card mb-3">
<div class="card-body">
<p>{{ comment.detail }}</p>
<p>{{ comment.user.username }}</p>
</div>
</div>
{% endfor %}
Your comments are connected to answers in a many-to-one relation, so you can get them in the same way as you access answers from questions - you just need to do it within a nested loop, i.e.
{% for answer in object.answer_set.all %
// your answer template stuff
{% for comment in answer.comment_set.all %}
// your comment template stuff
{% endfor %}
{% endfor %}
you need use object_list in your template has the docs of ListView says
<div class="col-11">
{% for answer in object_list %} <!-- change to object_list-->
<p>{{ answer.detail }}</p>
<p>{{ answer.user.username }}
<span>5 комментариев</span></p>
{% endfor %}
</div>
if need/wanna change the name of the variable object_list, you can define context_object_name in the view.

How can show categories products in home page?

I want to show my all products row by row on the home page category-wise. Suppose, before starting a new row will have a heading(category name) and then will show all products according to the product category and then again will be starting a new row with a heading according to category. How can I do it? I applied the 3/4 methods but didn't work. Bellow, I've shown one of the methods. It doesn't work properly. Please help me.
views.py:
def home(request):
all_products = Products.objects.all()
context ={
"all_products":all_products,
}
return render(request,'home.html', context)
model.py:
class Category(models.Model):
title = models.CharField(blank=True, null=True, max_length = 100)
def __str__(self):
return str(self.title)
class Products(models.Model):
product_image = models.ImageField(blank=True, null=True, upload_to = "1_products_img")
product_title = models.CharField(blank=True, null=True, max_length = 250)
product_price = models.IntegerField(blank=True, null=True)
offer_price = models.IntegerField(blank=True, null=True)
created_date = models.DateTimeField(blank=True, null=True, auto_now=True)
product_category = models.ForeignKey(Category, related_name="categoty_related_name", on_delete=models.CASCADE, blank=True, null=True)
context_processors.py:
from .models import Category
def categories(request):
return {"categories":Category.objects.all()}
template:
{% for products in all_products %}
<h4 class="text-start montserrat_alternates_font ">{{products.product_category}}</h4>
<hr>
<!--- product card --->
<div class="col mb-5">
<div class="card h-100">
<!-- Sale badge-->
{% if products.offer_price != None %}
<div class="badge bg-dark text-white position-absolute" style="top: 0.5rem; right: 0.5rem">
SALE
</div>
{% endif %}
<!-- Product image-->
<img class="card-img-top" style="height:150px;" src="{{products.product_image.url}}" alt="..." />
<!-- Product details-->
<div class="card-body ">
<div class="text-center">
<!-- Product name-->
<h5 class="fw-bolder product_title" style="">{{products.product_title}}</h5>
</div>
</div>
</div>
</div>
{% endfor %}
You have to change your thinking a bit :)
The easiest approach is to think from bigger "items" (like your Category) to smaller (Product).
Firstfully, use singular naming for models, like Product (without 's', just like you did with Category), because otherwise you will end up with confusing setting. Also, don't name fields with your model's name unless it's really necessary, product_category should be simply category and so on.
Secondly, open big for loop with Category that you pass as classic view's context:
def home(request):
categories = Category.objects.all()
context = {
"categories": categories,
}
return render(request,'home.html', context)
Then look at your ForeignKey field in Product:
class Products(models.Model):
...
category = models.ForeignKey(Category, related_name="products", ...)
The key is to use related name properly. In your template, try logic like this (using related_name smoothly in reversed relationship):
{% for category in categories %}
{{ category.title }}
{% for product in category.products.all %}
{{ product.title }} - {{ product.price }}
{% endfor %}
{% endfor %}
If you don't set related_name, then it would be accessible with <model_name>_set.all(), in this case product_set.all() (in templates without brackets, of course). Anyway, it will work like a charm :)

Django displaying related objects

I have models for ProjectNotes and for ProjectNotesComments. ProjectNotesComments have a foreign key that is the ProjectNotes id. I am able to save comments on notes. I can see them in the admin panel.
However I have not been able to figure out how to display the comments on the notes.
Here are the models:
class ProjectNotes(models.Model):
title = models.CharField(max_length=200)
body = tinymce_models.HTMLField()
date = models.DateField(auto_now_add=True)
project = models.ForeignKey(Project, default=0, blank=True, on_delete=models.CASCADE, related_name='notes')
def __str__(self):
return self.title
class ProjectNoteComments(models.Model):
body = tinymce_models.HTMLField()
date = models.DateField(auto_now_add=True)
projectnote = models.ForeignKey(ProjectNotes, default=0, blank=True, on_delete=models.CASCADE, related_name='comments')
Here is the view:
class ProjectNotesDetailView(DetailView):
model = ProjectNotes
id = ProjectNotes.objects.only('id')
template_name = 'company_accounts/project_note_detail.html'
comments = ProjectNotes.comments
This is the template I am currently using to test getting the comments to display:
{% extends 'base.html' %}
{% block content %}
<div class="section-container container">
<div class="project-entry">
<h2>{{ projectnotes.title }}</h2>
<p>{{ projectnotes.body | safe }}</p>
</div>
<div>
</div>
{% for comment in comments %}
<div class="comments" style="padding: 10px;">
<p class="font-weight-bold">
{{ comment.body | linebreaks }}
</div>
{% endfor %}
<h2>add note</h2>
{% endblock content %}
I don't think this actually works: comments = ProjectNotes.comments. You would need to override the get_context_data method, and set comments on the context_data to accomplish what you are attempting to do there.
However, you don't need to do that at all, since you can get to the comments from the ProjectNotes object, and the ProjectNotes object is already in the context. Simply change your for loop to this:
{% for comment in projectnotes.comments %}

Django - Models: for loop in query/filter

I have a list of reviews and each review has a rating average. My problem is trying to added each review id to the filter for the query result. For this, I assume a for loop in the filter is best.
I've found a previous post with a similar situation, but the same result doesn't seem to be working for me.
When I load my reviews page, I receive a TypeError: 'function' object is not iterable.
Here is my view.py file with the queries.
def reviews(request):
context = {
'reviews': Employee.objects.all(),
'rating': Employee.objects.filter(id__in=[review.id for review in reviews]).aggregate(rate=Avg(F('value1')+F('value2')+F('value3').....+F('valueN'))/N)
}
return render(request, 'reviews/reviews.html', context)
Reviews.html template.
{% extends "reviews/layout.html" %}
{% block content %}
{% for review in reviews %}
{% for rating in ratings %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ review.author.profile.image.url }}">
<div class="media-body">
<div class="article-metadata">
<h4 class="mr-2">{{ review.company }} {{rating}}</h4>
<small class="text-muted">{{ review.date_posted|date:"F d, Y" }}</small>
</div>
<h5><a class="article-title" href="{% url 'review-detail' review.id %}">{{ review.title }}</a></h5>
<p class="article-content">{{ review.content }}</p>
</div>
</article>
{% endfor %}
{% endfor %}
{% endblock content %}
Any suggestions are much appreciated.
EDIT: Here is my model for the Employee table.
class Employee(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
company = models.CharField(max_length=100)
recommend = models.BooleanField(default=False)
salary = models.CharField(max_length=100)
salary_satis = models.CharField(max_length=100)
culture = models.CharField(max_length=100)
location = models.CharField(max_length=100)
work_env = models.CharField(max_length=100)
communication = models.CharField(max_length=100)
opportunity = models.CharField(max_length=100) # Opportunity happiness
leadership_satis = models.CharField(max_length=100)
fair_treatment = models.CharField(max_length=100)
advice = models.TextField() # Advice for management
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
ordering = ['-date_posted']
def __str__(self):
return f'{self.title, self.content, self.company, self.recommend, self.salary, self.salary_satis, self.culture, self.location, self.work_env, self.communication, self.opportunity, self.leadership_satis, self.fair_treatment, self.advice, self.date_posted, self.author}'
def get_absolute_url(self):
# Returns user to reviews page after their review has been submitted.
return reverse('reviews')
Your reviews in the dictionary, refers to the reviews view function (here both in boldface):
def reviews(request):
context = {
'reviews': Employee.objects.all(),
'rating': Employee.objects.filter(
id__in=[review.id for review in reviews]
).aggregate(
rate=Avg(F('value1')+F('value2')+F('value3').....+F('valueN'))/N
)
}
return render(request, 'reviews/reviews.html', context)
Indeed, you can not iterate over this function, since it has no __iter__ method attached to it, but even if it had, this is not what you want to do.
I think the most elegant way to solve this, is simply defining a reviews variable:
def reviews(request):
reviews = Employee.objects.all()
context = {
'reviews': reviews,
'rating': Employee.objects.filter(
id__in=[review.id for review in reviews]
).aggregate(
rate=Avg(F('value1')+F('value2')+F('value3').....+F('valueN'))/N
)
}
return render(request, 'reviews/reviews.html', context)
So now reviews is a local variable that you can acces, and since a queryseet is iterable, we can iterate over this.
That being said, it is a bit odd to use an id__in here, since reviews has all the Employees.

Django template loop through items with parent ID or PK

I'm trying to set up magnific popup on django.
My goal is to have one main picture in the homepage overview gallery view, which when clicked, would open a popup with the related images from the same photoshoot i.e. images with the same ID or PK.
I tried to apply the following approach
but i just cannot get it to work, maybe someone could help me out in this
My models.py
class Item(models.Model):
name = models.CharField(blank=False, max_length=200)
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.SET_NULL)
order = models.IntegerField(blank=True, null=True)
active = models.BooleanField(blank=True, default=False)
objects = models.Manager()
class Meta:
verbose_name_plural = 'items'
def __str__(self):
return self.name
class ItemImage(models.Model):
image = ProcessedImageField(
blank=True,
null=True,
processors=[ResizeToFit(width=1680, upscale=False)],
format='JPEG',
options={'quality':90})
order = models.IntegerField(blank=True, null=True)
main = models.BooleanField(blank=True, default=False)
cover = models.BooleanField(blank=True, default=False)
item = models.ForeignKey(Item, related_name='items', blank=True, null=True, on_delete=models.SET_NULL)
objects = models.Manager()
class Meta:
verbose_name_plural = 'item images'
Views.py
def portraits(request):
port = ItemImage.objects.filter(item__category__slug='portraits', item__active=True, main=True,).order_by('item__order')
portall = ItemImage.objects.filter(item__category__slug='portraits', item__active=True).order_by('item__order')
context = {
'main_portraits': port,
'all_portraits': portall
}
return render(request, 'gallery/portraits.html', context)
and Template:
{% block content %}
<div class="grid">
{% for pic in main_portraits %}
<div class="item">
<div class="item">
<div class="outer-text">
<div class="text">
{{ pic.item.name }}
<p>Click to view gallery</p>
</div>
</div>
<a><img class="lazy" alt=""
sizes="(min-width:1400px) 1220px
(min-width:1000px) 1000px,
(min-width:500px) 700px,
(min-width:320px) 420px,
280px"
srcset="{{ pic.image_xs.url }} 280w,
{{ pic.image_s.url }} 420w,
{{ pic.image_m.url }} 700w,
{{ pic.image_l.url }} 1000w,
{{ pic.image_xl.url }} 1220w" />
</a> {{ pic.item.pk }}
</div>
<div class="lazy">
{% for p in all_portraits %}
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% endblock %}
I have set
z.item.pk
just as a test, to see if any of my manipulations result in the pk's to bunch up. For example the first for-loop returns a picture with PK "24", I need for the second for-lop to return only images with the same PK; and so for every image. I think the answer might be connected with _set.all function, just like in the related question above, but I cant seem to get it to work in my case. Feels like I'm missing something here.
current output:
<div class="grid">
<div class="item">
<div class="item">
<div class="outer-text">
<div class="text">
Palagā tītā
<p>Click to view gallery</p>
</div>
</div>
<a><img class="lazy" alt=""
sizes="(min-width:1400px) 1220px
(min-width:1000px) 1000px,
(min-width:500px) 700px,
(min-width:320px) 420px,
280px"
srcset="/media/CACHE/images/IMG_8329_3Vi8mYO_GD621ql/958ba5dbee5efe28fd2f5054b8f819e1.jpg 280w,
/media/CACHE/images/IMG_8329_3Vi8mYO_GD621ql/02d12ca7f0633fee2fc762cf96f7889e.jpg 420w,
/media/CACHE/images/IMG_8329_3Vi8mYO_GD621ql/ba5fa6633e92a288e3b2f47a713d64c2.jpg 700w,
/media/CACHE/images/IMG_8329_3Vi8mYO_GD621ql/fe0d559fef5b02434c43f841005d4961.jpg 1000w,
/media/CACHE/images/IMG_8329_3Vi8mYO_GD621ql/96d0e52dff14d1bc4b60bbec674565db.jpg 1220w" />
</a> 24
</div>
<div class="lazy">
</div>
</div>
You need prefiltered querysets containing the related images for every main image before handing over to the template.
def portraits(request):
ports = ItemImage.objects.filter(item__category__slug='portraits', item__active=True, main=True,).order_by('item__order')
for p in ports:
# You may not need the item__category__slug filter
# if there are only images of the same category
# associated with an item.
# Also, if you want to exclude the main image
# from the set of related images, you need to add the filter
# main=False
p.related_images = ItemImage.objects.filter(item__category__slug='portraits', item__id=p.item.id)
context = {
'main_portraits': ports,
}
return render(request, 'gallery/portraits.html', context)
Then you can loop over main_portraits in the template, and get the related images for each main image in a nested loop:
{% for mainp in main_portraits %}
{% for im in mainp.related_images %}
{# do something with the related images #}
{% endfor %}
{% endfor %}
You can break down the models like this it will make the querying easier.
# models.py
class Item(mdoels.Model):
name = models.CharField(blank=False, max_length=200)
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.SET_NULL)
...
display_image = models.ProcessedImageField(...)
class ItemImage(models.Model):
item = models.ForeignKey(Item, related_name='images', blank=True, null=True, on_delete=models.SET_NULL)
image = models.ProcessedImageField(...)
...
#views.py
def portraits(request):
items = Item.objects.filter(category__slug='portraits', active=True)
return render(request, 'gallery/portraits.html', context={items: items})
#template
{% for item in items %}
<h1> {{item.name}} </h1>
<img src={{item.display_image}} />
{% for item_image in item.images.all %}
<img src={{item_image.image}} />
{% endfor %}
{% endfor %}