Django tree query spanning multi model relation - python

Please consider code the following code. I'd like to create something like a tree structure (it will be in a table) related to a specific loged-in user. I'd like to have an overviewpage in which all categories are shown for which this user has items in the subcategories. My query works, but gives me all subcategory records and all posible items in those subcategories (so not just the subset for which the user has records.
I have another way working by starting with the itemmodel and then going up in the tree, but thats not what i'm looking for (this makes creating a dynamic set of tables far to depended on the django template layer).
I've looked at select_related and prefetch, but I can't get close to a solution. Any help would be appriciated.
models.py:
from django.db import models
from model_utils.models import TimeStampedModel
from django.conf import settings
User = settings.AUTH_USER_MODEL
class MainCat(TimeStampedModel):
name = models.CharField(max_length=100, unique=True)
rank = models.IntegerField(default=10)
icon_class = models.CharField(max_length=100, null=True, blank=True)
class SubCat(TimeStampedModel):
name = models.CharField(max_length=100, null=False, blank=False)
slug = models.SlugField(null=True, blank=True)
icon_class = models.CharField(max_length=100, null=True, blank=True)
main_cat = models.ForeignKey(MainCat)
class Item(TimeStampedModel):
user = models.ForeignKey(User, blank=True, null=True)
item_name = models.CharField(max_length=100, null=False, blank=False)
sub_cat = models.ForeignKey(SubCat, blank=True, null=True)
views.py:
from django.views.generic import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import MainCat
class MainCatByUserListView(LoginRequiredMixin, ListView):
model = MainCat
def get_queryset(self):
qs = super(MainCatByUserListView, self).get_queryset()
qs = qs.filter(subcat__item__user=self.request.user)
return qs
template:
< html >
< body >
{% for maincat in object_list %}
< ul >
<li> {{maincat.name}} </li>
<ul>
{% for subcat in maincat.subcat_set.all %}
<li> {{subcat.name}} </li>
{% for item in subcat.item_set.all %}
<li> {{item.name}} </li>
</ul>
</ul>
{% endfor %}
{% endfor %}
{% endfor %}
< / body >
< / html >
{% endfor %}

You should be able to use something like: Subcat.objects.filter(item_set__user=self.request.user).select_related('main_cat')

Finaly got to an answer to my question. For future reference:
class MainCatListView(LoginRequiredMixin, ListView):
model = MainCat
template_name = 'app/main_cat_overview.html'
def get_queryset(self):
qs = super(MainCatListView, self).get_queryset()
qs_user_cat_set = SubCat.objects.filter(item__user=self.request.user).prefetch_related(Prefetch(
"item_set",
queryset=Item.objects.filter(user=self.request.user),
to_attr="item_attr")
).distinct()
qs = qs.filter(subcat__item__user=self.request.user).prefetch_related(Prefetch(
"subcat_set",
queryset=qs_user_subcat_set,
to_attr="subcat_attr")
).distinct()
return qs

Related

How to refrence to an item in WishList for a Product in Django?

I want to implement a wishlist for the products in my Django site so that I can represent them in a wishlist page to the user.
the products are in the products app.
products.models.py
class ControlValves(models.Model):
title = models.CharField(max_length=50, unique=True)
product_name = models.CharField(max_length=50, blank=True)
....
class Accessories(models.Model):
title = models.CharField(max_length=50, unique=True)
description = models.CharField(max_length=50, blank=True)
....
There is a services app that contains various services(models).
Then I want to create a wishlist in users app.
users.models.py
class Wishlist(models.Model):
owner = models.ForeignKey( User, on_delete=models.CASCADE)
title = models.CharField(max_length=50, null=True, blank=True)
item = models.ForeignKey( which_model_should_be_here??? , on_delete=models.CASCADE)
since I am fetching the list of products and services from two different apps and within each, there are various models:
question: 1- I don't know how to point to the selected product or service? should I declare a foreign key to each possible product model o services model, or there is another efficient way?
2- an example of how to load them to show to the user( i.e. how to write a query to load them from DB) would be great.
I also checked the answer to this and this, but they are fetching products from just one model, so it's easy because there should be just one foreign key.
I think a simple ManyToMany relation should work here. For example:
class Wishlist(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50, null=True, blank=True)
accessories = models.ManyToManyField(Accessories)
services = models.ManyToManyField(ControlValves)
And to show them in template, you can simply use:
{% if user.is_authenticated %}
{% for wish in user.wishlist_set.all %}
{{ wish.title }}
{% for accessory in wish.accessories.all %}
{{ accessory.title }}
{% endfor %}
{% for service in wish.services.all %}
{{ service.title }}
{% endfor %}
{% endfor %}
{% endif %}
Update based on comments
If you have 10 model classes which can be added to the WishList model, then above approach won't work. Consider using the following:
class Wishlist(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50, null=True, blank=True)
class WishListItem(models.Model):
wishlist = models.ForeignKey(WishList, on_delete=models.DO_NOTHING, related_name='wishitems')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
Reson for this is that it is not practical to have 10 m2m relation. Instead using GenericForeginKey to add any of the models you want to add to the WishListItem instance.
Now if you want to display them in template, then use the following code:
{% if user.is_authenticated %}
{% for wish in user.wishlist_set.all %}
{{ wish.title }}
{% for wishitem in wish.wishitems.all %}
{{ wishitem.content_object }}
{% endfor %}
{% endfor %}
{% endif %}
FYI, reason for M2M or adding extra model for items is that user can have multiple wishlists, so it is better to hold them in on DB table, and items in different.
This is just but my suggestion:
create a list of types which represent product model
types=((0, "ControlValves"),(1, "Accessories"))
class Wishlist(models.Model):
owner = models.ForeignKey( User, on_delete=models.CASCADE)
item_id = models.IntegerField()
item_type = models.IntegerField(choices=types)
Then on your views.py
def get_wishlist(request):
allList = models.Wishlist.objects.filter(ownder=ownder_id)
items = []
for listItem in allList:
if listItem.item_type==0:
product = models.ControlValves.objects.get(pk=listItem.item_id)
item = {"id": listItem.id, "title": product.title, ...}
items.append(item)
elif listItem.item_type==0:
product = models.Accessories.objects.get(pk=listItem.item_id)
item = {"id": listItem.id, "title": product.title, ...}
items.append(item)
return HttpResponse(items)

How to send multiple model from a classbased view in django

I made a booklist where cover image can be uploaded inside Booklist class. For more image I added another class called Bookcover. Now in Views.py how can I send both Booklist and Bookcover's by using BookListView
models.py file is below
from django.db import models
from django.utils import timezone
class Booklist(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length = 100)
cover = models.ImageField(null=True, blank=True, default='default-book.jpg')
description = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
price = models.DecimalField(decimal_places=3, max_digits=100)
def __str__(self):
return self.title
class Bookcover(models.Model):
post = models.ForeignKey(Booklist, default=None, on_delete=models.CASCADE)
covers = models.ImageField(upload_to = 'images/')
def __str__(self):
return self.post.title
here is views.py file
from django.shortcuts import render
from django.views.generic import ListView
from .models import Booklist, Bookcover
def home(request):
return render(request, template_name='home/index.html')
class BookListView(ListView):
model = Booklist
template_name = 'home/index.html'
context_object_name = 'books'
ordering = ['-date_posted']
If you make a ForeignKey, Django automatically will generate a relation in reverse to access - in this case - the related BookCovers for a specific Book. Since you did not specify the related_name=… parameter [Django-doc], the name of this relation is modelname_set, so in this case, bookcover_set.
In the template you can access the book covers of a book with:
{% for book in books %}
{{ book.title }}
{% for cover in book.bookcover_set.all %}
<img src="{{ cover.covers.url }}">
{% endfor %}
{% endfor %}
This will result in an N+1 problem however. You can avoid that by using .prefetch_related(…) [Django-doc]:
class BookListView(ListView):
queryset = Booklist.objects.prefetch_related('bookcover_set')
template_name = 'home/index.html'
context_object_name = 'books'
ordering = ['-date_posted']

How to pass parameter in current url to an html template in Django 3

I have:
Movie Model
class Movie(models.Model):
title = models.CharField(max_length=255)
synopsis = models.TextField()
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('movie_detail', args=[str(self.id)])
Discussion Model
class Discussion(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
)
date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
movie = models.ForeignKey(
Movie,
on_delete=models.CASCADE,
related_name='discussion',
)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('discussion_detail', args=[str(self.movie.id), str(self.id)])
DiscussionListView
class DiscussionListView(LoginRequiredMixin, ListView):
model = Discussion
template_name = 'discussion_list.html'
login_url = 'login'
And I also have discussion_list.html
Here is what I want:
I’m in the url /article/1/discussion/. The integer 1 is a movie_pk because the url is defined as /article/<int:movie_pk>/discussion/ which in this case refer to the priority key of a Movie Model, for example StarWars I. This is a page of list of discussion titles related to this movie. (This has already been achieved)
There is a button “New” where, if i click on it, I will be directed to /article/1/discussion/new. There, I can create a new discussion. (The feature I want to add)
However:
In discussion_list.html, we require the url tag {% url discussion_new %} to have a parameter since discussion_new is defined as /article/<int:movie_pk>/discussion/new
Thus:
How to pass the movie_pk from the current url, then to DiscussionListView, then to the template discussion_list.html?
In detail view's template, you can send the object.id as parameter of url:
#template
{% url "discussion_new" movie_pk=object.pk %}
If you are in a List View template then probably you have a loop to go through all object_list. There you can implement like this:
{% for object in object_list %}
{% url "discussion_new" movie_pk=object.pk %}
{% endfor %}
{% url discussion_new %}
to
{% url "discussion_new" pk=movie_pk%}
And in list
{% for m in movie_list %}
{% url "discussion_new" movie_pk=m.pk %}
{% endfor %}

Can I use filter() in django template?

What I'd like to do is using filter() in django template like belows:
models.py
from django.db import models
From Django. utils import Timezone
class Category(models.Model):
url = models.CharField(max_length=200)
site_name = models.CharField(max_length=50)
board_name = models.CharField(max_length=50)
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField(blank=True)
category = models.ForeignKey(Category)
created_date = models.DateField(blank=True, null=True)
crawl_date = models.DateTimeField()
num_of_comments = models.PositiveSmallIntegerField(default=0, blank=True, null=True)
notice = models.BooleanField(default=False)
views.py
def post_list(request, site_name=None, page=1):
categories = Category.objects.filter(site_name=site_name.upper()).order_by('board_name')
return render(request, 'SNU/post_list.html', {'site_name':site_name.upper(), 'categories':categories})
post_list.html
{% for category in categories %}
<p> {{category}} </p>
{% for post in category.post_set.filter(notice=True) %}
<li>{{ post.title }}</li>
{% endfor %}
{% endfor %}
In post_list.html, {% for post in category.post_set.filter(notice=True) %} occurs error. Only category.post_set.all is the one that I can use in template?
You can do it on view level by
def post_list(request, site_name=None, page=1):
categories = Category.objects.filter(site_name=site_name.upper(),
notice=True).order_by('board_name')
return render(request, 'SNU/post_list.html',{'site_name':site_name.upper(),
'categories':categories})
If for some reason you need all categories, not just those having notice = True,
add one more query without notice=True in filter and pass it in the dictionary.
Alternatively, you can create custom tag and provide filter - see https://docs.djangoproject.com/en/1.9/howto/custom-template-tags/

Display foreign key value in django template

I've looked through the similar questions and was unable to find a solution that fits or I'm missing something? I have two models(SafetyCourse and SafetyCourseTaken) I have a foreign key relationship that points from "safety courses taken" to safety course, shown below:
models.py
class SafetyCourse(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(max_length=128, unique=True)
def __str__(self):
return self.name
class SafetyCoursesTaken(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
profile = models.ForeignKey(EmployeeProfile, on_delete=models.CASCADE)
course = models.ForeignKey(SafetyCourse, on_delete=models.CASCADE, related_name='course_name')
conducted_date = models.DateTimeField(null=True, blank=True)
expiration_date = models.DateTimeField(null=True, blank=True)
class Meta:
verbose_name_plural = 'Safety Courses Taken'
views.py
class ManageSafetyCourseTakenView(LoginRequiredMixin, generic.ListView):
login_url = reverse_lazy('users:login')
model = SafetyCoursesTaken
template_name = 'ppm/courses-taken.html'
paginate_by = 10
# override get_queryset to only show training related to employee profile
def get_queryset(self):
pk = self.kwargs['pk']
return SafetyCoursesTaken.objects.filter(profile_id=pk)
course-taken.html(template)
{% for course_taken in object_list %}
<tr>
<td>{{ course_taken.course_id}}</td>
</tr>
{% endfor %}
I've tried a number of solutions to similar questions but was unable to find a correct one. I've tried: course_taken.course_name_set.select_related, course_taken.course_name_set, and a few others. What I want to do is just display the name of the course instead of the course id. What am I doing wrong?
Looking at your schema, I think it should be this in the template:
{% for course_taken in object_list %}
<tr>
<td>{{ course_taken.course.name }}</td>
</tr>
{% endfor %}

Categories