Best way to show data from multiple tables - django

I have a recurring problem that I can't seem to solve adequately. My site is akin to a job site, where people can post jobs (and details within), and other people can bookmark tthem. Each job can obviously be bookmarked by more than one viewer.
So here's the model.py:
class Job(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=150)
description = models.CharField(max_length=5000)
class Bookmark(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
job = models.ForeignKey(Job, on_delete=models.CASCADE)
My intention is to display all jobs in the homepage, that means multiple jobs from multiple users. Other users can click on a little bookmark icon to bookmark it, or to unbookmark it. Like so:
Here's the view.py:
def index(request):
context = {}
populateContext(request, context)
jobs = Job.objects.all().order_by('-id')
context = {'jobs': jobs}
return render(request, 'templates/index.html', context)
Pretty simple I think, which is my intention. Here's how the template looks:
{% for job in jobs %}
<div>
{{ job.title }}<br>
{{ job.description }}<br>
by {{ job.user.username }}
</div>
{% endfor %}
My question is, how do I display the bookmark state specific to each user? Here's my current solution:
{% for job in jobs %}
<div>
{{ job.title }}<br>
{{ job.description }}<br>
by {{ job.user.username }}<br>
{% for watch in job.bookmark_set.all %}
{% if watch.user_id = request.user.id %}
You have bookmarked this! <a>Unbookmark!</a>
{% endif %}
{% endfor %}
<a>Bookmark</a>
</div>
{% endfor %}
The "Unbookmark!" link will be positioned over the "Bookmark" link by the way. The solution above works that way, because the bookmark table will contain zero or more bookmarks for a particular job, but under multiple users. I would handle this in the view.py, by filtering just jobs that the logged-in user has made. But I can't filter by which job specifically, because the index.html will be displaying them all. So for example, I could spit this out...:
bookmarked = Bookmark.objects.all().filter(user_id=request.user.id)
...and that would list out all bookmarks on different jobs that the logged-in user had made. I still need to filter that in the template, so that each project matches with each bookmark, and I understand this isn't possible.
Anyway, I think this is pretty inefficient. So I was wondering if there was an easier way to handle this? Preferably so that it works this way:
{% for job in jobs %}
<div>
{{ job.title }}<br>
{{ job.description }}<br>
by {{ job.user.username }}<br>
{% if job.id = bookmark.job.id %}
You have bookmarked this! <a>Unbookmark!</a>
{% else %}
<a>Bookmark</a>
{% endif %}
</div>
{% endfor %}
Thank you!

You can use conditional expressions to annotate your jobs with this information. In the view:
from django.db.models import Case, IntegerField, Sum, When
jobs = Job.objects.annotate(
is_bookmarked=Sum(Case(
When(bookmark__user=request.user, then=1),
default=0, output_field=IntegerField()
))).order_by('-id')
Each job now has an is_bookmarked property which is either 1 (the user has bookmarked the job) or 0. In your template:
{% for job in jobs %}
<div>
{% if job.is_bookmarked %}
You have bookmarked this! <a>Unbookmark!</a>
{% else %}
<a>Bookmark</a>
{% endif %}
</div>
{% endfor %}
Just for completeness, the other approach you had in mind would also work (although less efficient than the one above). In the view:
jobs = Job.objects.all().order_by('-id')
# Get a list of all Job IDs bookmarked by this user
user_bookmarks = Bookmark.objects.filter(user_id=request.user.id)\
.values_list('job__id', flat=True)
In the template:
{% for job in jobs %}
<div>
{% if job.id in user_bookmarks %}
You have bookmarked this! <a>Unbookmark!</a>
{% else %}
<a>Bookmark</a>
{% endif %}
</div>
{% endfor %}
Both these approaches are doing pretty much the same logic - the difference being that the first one does this at database level which is generally more efficient.

Related

Receiving a list of related models from the list of objects (bound by a foreign key). Django

I have two very simple classes for products and photos. I would like to present products on the main page of my store along with photos related to a foreign key. But I do not know how to do this, I found many answers to get this in the view using a join like 'prefetch_related' but my ID changes for each case. So how do you present your photos for each product? are there any Django tags about which I do not know?
my models.py
class Product(models.Model)
name = models.CharField(max_length=10)
class Image(models.Model)
image = models.ImageField()
name_related = models.ForeignKay(Product, on_delate=models.CASCADE)
views.py
def home(request):
product_list = Product.objects.all()[:12]
#img = ??
context = {'product_list': product_list,
}
return render(request, 'home.html', context)
home.html
{% for product in product_list %}
{{ product.name }}
<!-- {{ product.imge }} ' element presenting the first photo for each 'product' model'???-->
{% endfor %}
Any help will be appreciated.
Since a foreign relation has been established you can iterate through the related models from the parent model.
The related models are already accessible from the parent model without doing anything explicit.
In your template you can do this:
{% for product in product_list %}
{{ product.name }}
{% for product_image in product.image.all %}
<!-- Iterate through this products related images -->
{{ product_image.image.url }}
{% endfor %}
{% endfor %}
For performance reasons, you will want to prefetch the relations, or else for every product you will do an additional query, so, in your view you will want to add this:
product_list = Product.objects.all().prefetch_related('image')[:12]
Just do
{% for product in product_list %}
{{ product.name }}
{% for image in product.image.all %}
<!-- {{ image.image.url }} -->?
{% endfor %}
{% endfor %}

Django Template Tag - Display only one value in nested for loop

I'm working on a Django web app and have the following query:
I have a model called 'AppQoSList' which lists the applications available to all users.
I have then another model called 'BasicAppSDWANProfiles' which has a ManyToMany relationship with 'AppQoSList' .
In short, it means a user can have multiple 'BasicAppSDWANProfiles' associated to his account and multiple AppQoS can be within a particular BasicAppSDWANProfiles:
class AppQoSList(models.Model):
app_qos_name = models.CharField(max_length=50, blank=None, null=True)
app_qos_description = models.CharField(max_length=500)
def __str__(self):
return u'%s' % self.app_qos_name
class BasicAppSDWANProfiles(models.Model):
profile_name = models.CharField(max_length=30)
profile_basic_app_qos = models.ManyToManyField(AppQoSList)
tenant_id = models.ForeignKey(Tenant, default=3)
I'm facing issue in my template when I try to display the list of apps available and the associated BasicAppSDWANProfile:
{% for app in apps %}
{% for profile_app in sdwan_prof %}
{% for specific_app in profile_app.profile_basic_app_qos.all %}
{% ifchanged specific_app.pk %}
{% if app.pk == specific_app.pk %}
<td><h4><span class="label label-primary">{{ profile_app.profile_name }}</span></h4></td>
{% else %}
<td><h4><span class="label label-warning">Not Assigned</span></h4></td>
{% endif %}
{% endifchanged %}
{% endfor %}
{% endfor %}
{% endfor %}
Issue with this code is that 'Not Assigned' is displayed 6 times on each row (which corresponds to the number of Apps found in BasicAppSDWANProfiles associated with this user) whereas I would like to display it only once:
Would you have any solution for this ?
Thanks in advance.
I was able to address this issue.
First I did clean up my view code to remove duplicate 'Not Assigned' values.
I pass to my template context a dictionary with only apps that have a profile assigned such as below:
{'citrix-static': 'DPS-BLACKLIST',
'exchange': 'DPS-BLACKLIST',
'ms-lync-audio': 'DPS-WHITELIST',
'ms-update': 'DPS-GREYLIST',
'rtp': 'DPS-WHITELIST',
'share-point': 'DPS-WHITELIST'}
In my template, I only loop through this dictionary:
{% for k,v in app_prof_assign.items %}
{% if app.app_qos_name == k %}
<td><h4><span class="label label-primary">{{ v }}</span></h4></td>
{% endif %}
{% endfor %}
I then simply check if the app is not in the profile dictionary, outside the loop:
{% if app.app_qos_name not in app_prof_assign %}
<td><h4><span class="label label-warning">Not Assigned</span></h4></td>
{% endif %}
Finally, I can get the table populated as expected:

Creating a Hierarchy View in Django

I'm trying to create a hierarchy view in Django, but I'm struggling to make sense of how to use QuerySets effectively.
What I'm aiming for eventually is a html page that displays courses like this:
Main Course 1 --- Child Course 1
--- Child Course 2
Main Course 2 --- Child Course 3
--- Child Course 4
Each group of courses would be wrapped in a div and styled etc.
In my view.py file I have the following:
class HierarchyView(generic.ListView):
template_name = 'curriculum/hierarchy.html'
def get_queryset(self):
return Offering.objects.all()
def get_context_data(self, **kwargs):
context = super(HierarchyView, self).get_context_data(**kwargs)
context['main'] = self.get_queryset().filter(course_type='M')
context['sub'] = self.get_queryset().filter(parent_code__in=context['main'])
return context
The Offering model is set up so that parent_code is a self-referential foreign key (i.e. any course can be a child of any other), like this:
...
parent_code = models.ForeignKey(
'self',
null=True,
blank=True,
on_delete=models.SET_NULL)
...
And in my html template I have:
{% for mainoffering in main %}
<div>
<div>{{ mainoffering.course_name }}</div>
{% for offering in sub %}
<div>{{ offering.course_name }}</div>
{% endfor %}
</div>
{% endfor %}
What this results in, however, is that all child courses appear under all main courses, regardless of whether or not they are actually children of that course, which is obviously not what I'm after.
I'm still learning the ropes in Django, and I'm struggling to find anything that explains in plain English what I need to do. Please help!
I think you would need to change your template to match each of the child courses to their parent courses. Maybe something like:
{% for mainoffering in main %}
<div>
<div>{{ mainoffering.course_name }}</div>
{% for offering in sub %}
{% if offering.parent_code == mainoffering %}
<div>{{ offering.course_name }}</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
The context['sub'] will return all of them, without any grouping, ordering etc. You can do couple of things to get the desired behavior.
You can do a prefetch related.
from django.db.models import Prefetch
offerings = Offering.objects.filter(course_type='M').prefetch_related(
Prefetch(
"courses_subset",
queryset=Offering.objects.filter(parent_code__in=offerings),
to_attr="sub"
)
)
for o in offerings:
print o.sub
You can actually make this a method in your model and create a template tag (i'd most likely use this).
method in your Offering model
def get_child_courses(self):
child_courses = Offerings.objects.filter(parent_code=self.id)
return child_courses
template tag
#register.simple_tag
def get_child_courses(course):
return course.get_child_courses()
In your template:
{% for mainoffering in main %}
<div>
<div>{{ mainoffering.course_name }}</div>
{% for offering in mainoffering|get_child_course %}
<div>{{ offering.course_name }}</div>
{% endfor %}
</div>
{% endfor %}
You can group them in your template as suggested by accraze. I'd personally go for the second option

Django: one-to-many relationship and reverse lookup

i have a Problem which i am unsure what the most django/pythonic way to solve it would be.
I have the following models:
class Order(models.Model):
ord_someinformation = models.CharField(max_length=10)
class Articles(models.Model):
myFK = models.ForeignKey(Order)
detail_article= models.CharField(max_length=10)
so every Order can have multiple Order_Details think about it like a shopping basket where i have an Order and within it i have multiple articles.
I want to iterate over the orders and the articles within the template. I thought about something like.
myOrder = Order.objects.("" i have no idea what to put here "")
within the template i thought about something like this:
{% for order in myOrder %}
{{ order.ord_someinformation }}
{% for articles in order.articles %}
{{ detail_article }}
{% endif %}
{% endif %}
is this possible?
If so how ?
I don't know why you think you need to put anything there. You just want to send all the orders to the template, then iterate through them their articles there.
myOrder = Order.objects.all()
...
{% for order in myOrder %}
{{ order.ord_someinformation }}
{% for article in order.articles_set.all %}
{{ article.detail_article }}
{% endif %}
{% endif %}

How to limit a collection of objects related on a foreign key in Django Templates?

Given is a model called "comment" with a foreign key relationship to a model called "task".
{% for task in tasks %}
{% for comment in task.comment_set.all %}
{{ comment }}
{% endfor %}
...
What is the best way to limit this to 5 comments like:
Entry.objects.all()[:5]
{% for task in tasks %}
{% for comment in task.comment_set.all|slice:"5" %}
{{ comment }}
{% endfor %}
{% endfor %}
You don't. You should not do "real work" in a template, this breaks the MVC pattern.
Do the real work in the view, and pass the data to the template (using the context dictionary).
def handle_comments(request):
tasks = Task.objects.all()
comments = {}
for task in tasks:
comments[task] = task.comment_set.all()[:5]
return render_to_response('commenting.html', {'comments': comments})
You can then iterate over the comments in your template:
{% for task, task_comments in comments.items %}{{ task }}{% endfor %}