Django: How to iterate over two one-two-many table relationships? - python

I am trying to build a simple web page that queries three tables. There is a Company table that has a one-to-many relationship with a Position table, as well as a one-to-many relationship with a Project table.
The goal is to have the page display a given company once, along with all positions and and projects associated with said company. Then, move on to display the next company, any positions held there and projects completed.
Below is the closest I've come to getting this right. But, the obvious problem is that if there is more than one project associated with a given company, you'll see that company listed more than once.
I'm new to Django, so in the interest of learning, I wanted to beat my own head sufficiently hard before asking for help; but I could really use some fresh ideas at this point.
Also: I can see how a nested for loop might work here, but I'm just not clear on how the mechanics of that would work with the query, and then within the template.
Models:
from django.db import models
class Company(models.Model):
company_id = models.AutoField(primary_key=True)
company_name = models.CharField(max_length=20)
company_logo = models.ImageField(upload_to='images/')
def __str__(self):
return self.company_name
class Position(models.Model):
position_id = models.AutoField(primary_key=True)
position_title = models.CharField(max_length=55)
company_id = models.ForeignKey('professional.Company',
on_delete=models.CASCADE,
blank=True,
null=True)
begin_date = models.DateField()
end_date = models.DateField()
def __str__(self):
return self.position_title
class Project(models.Model):
project_id = models.AutoField(primary_key=True)
project_name = models.CharField(max_length=55)
company_id = models.ForeignKey('professional.Company',
on_delete=models.CASCADE,
blank=True,
null=True)
project_description = models.CharField(max_length=500)
project_image = models.ImageField(upload_to='images/')
def __str__(self):
return self.project_name
View:
from django.views.generic import TemplateView, ListView
from professional.models import Company
class ProfessionalHome(TemplateView):
template_name = 'professional/professional_home.html'
class TechnologyListView(ListView):
template_name = 'professional/__technology.html'
context_object_name = 'technology_list'
def get_queryset(self):
return Company.objects.values('company_name','position__position_title', 'project__project_name')
HTML and template:
{% for job in technology_list %}
<h1>{{job.company_name}}</h1>
<h1>Position: {{job.position__position_title}}</h1>
<h1>project: {{job.project__project_name}}</h1>
{% endfor %}

Instead of values in get_queryset method, you can return the actual queryset and then iterate over it to build your view.
def get_queryset(self):
return Company.objects.all()
Then in your template:
{% for job in technology_list %}
<h1>{{job.company_name}}</h1>
{% for position in job.position_set.all() %}
<h1>Position: {{position.position_title}}</h1>
{% endfor %}
{% for project in job.position_set.all() %}
<h1>project: {{project.project_name}}</h1>
{% endfor %}
{% endfor %}

If you want to iterate over companies, then you should use the Company model as the basis for your view, not Technology. Also, you should avoid values and values_list unless you know you have a good reason, which you don't here. You can use prefetch_related() to reduce the number of reverse queries. So:
class TechnologyListView(ListView):
def get_queryset(self):
return Company.objects.all.prefetch_related('project','position')
...
{% for company in company_list %}
<h1>{{company.company_name}}</h1>
{% for position in company.position_set.all %}
<h1>Position: {{ position.position_title }}</h1>
{% endfor %}
{% for project in company.project_set.all %}
<h1>project: {{ project.project_name }}</h1>
{% endfor %}
{% endfor %}
(Note, you should avoid giving your ForeignKey fields names ending in "_id". The Django field refers to the entire Company, not the ID; the fields should be called just company. The underlying database will be suffixed with _id anyway. Also, you don't need to use model_name prefixes on all your fields; it will be obvious from the object they are accessed on.)

Related

Django - Can not join 2 models

Problem: Joining 2 models in Django.
Error: Error during template rendering. Direct assignment to the reverse side of a many-to-many set is prohibited. Use entity_id.set() instead.
I have read through all the threads on SO. Tried all the suggested solutions, read the Django documentation and think I just must be fundamentally misunderstanding something. Any help would be much appreciated.
I have 2 models. Entity and File.
An Entity can have multiples Files but each File only has 1 Entity.
The primary keys of each table are just auto incrementing integers. Therefore I want to join column entity_id from File with entity_id from Entity. According to the documentation I have set entity_id in File as a ForeignKey. And I have set entity_id as unique in Entity
class Entity(models.Model):
pk_entity = models.AutoField(primary_key=True)
entity_id = models.IntegerField(blank=True, null=True, unique=True)
name = models.CharField(blank=True, null=True)
class Meta:
managed = False
db_table = 'entities'
class File(models.Model):
pk_file = models.AutoField(primary_key=True)
filename = models.CharField(blank=True, null=True)
entity_id = models.ForeignKey(Entity, on_delete= models.CASCADE, to_field='entity_id')
The view is just trying to render this. I have tried using .all() rather than select_related() but no data renders.
class TestListView(ListView):
queryset = File.objects.select_related()
template_name = "operations/files/test_list.html"
And this is the html:
{% extends "base.html" %}
{% block content %}
<div>
<div>
<ul>
{% for x in object_list %}
<li>
{{x}}
</li>
{% empty %}
<p>Empty</p>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}
When using select_related you should pass your field name to be selected.
Try this:
class TestListView(ListView):
queryset = File.objects.select_related("entity").all()
template_name = "operations/files/test_list.html"

Retrieving metadata from a post, then sorting posts by said metadata in django

I have two interconnected models in my blog app; Category and Post. The blog front page displays a list of posts and their corresponding metadata, like it should; fairly standard stuff.
Aside from displaying the posts on the front page, they're also displayed on the individual user's profile page in short form (just the category and the headline).
What I'm interested in doing is sorting all the posts that belong in a category, however the only way I've managed to make it work is something like this:
NEWS
some title
NEWS
another title
PYTHON
another arbitrary title
NEWS
yet another title
I'd like to sort it thusly instead:
NEWS
some title
another title
yet another title
PYTHON
another arbitrary title
Alas, my mind keeps turning into a bowl of spaghetti when I try to come up with a method, so without further ado; how should I go about this bit?
I reckon that there's something off with calling the category from the post's metadata only to try and categorize the posts via the retrieved data, but aside from that, I'm somewhat lost.
Here's the template snippet from user_profile.html:
{% if user.post_set.exists %}
<p>
{% for post in user.post_set.all|dictsortreversed:"date_posted" %}
<span style="margin-right: 5px; padding: 3px 6px; border-radius:12px; color:#FFF; background-color:#FFA826;">{{ post.category }}</span><br/>
<a style="margin-left:3px;" href="{% url 'blog:post-detail' post.slug %}">{{ post.title|truncatechars_html:30 }}</a><br/>
{% endfor %}
</p>
{% endif %}
The models:
class Category(models.Model):
class Meta:
verbose_name = 'category'
verbose_name_plural = 'categories'
name = models.CharField(max_length=30)
def __str__(self):
return self.name
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=60)
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.CASCADE)
content = RichTextUploadingField(
external_plugin_resources=[(
'youtube',
'/static/ckeditor/ckeditor/plugins/youtube/',
'plugin.js'
)],
blank=True,
null=True,
)
date_posted = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(auto_now=True)
slug = models.SlugField(max_length=70, blank=True, null=True, help_text='<font color="red">don\'t. touch. the. slug. field. unless. you. mean. it.</font> (it will auto-generate, don\'t worry.)')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post-detail', kwargs={'slug': self.slug})
And finally the view which relate to the post_list.html:
class PostListView(ListView):
model = Post
template_name = 'blog/home.html'
context_object_name = 'posts'
ordering = '-date_posted'
paginate_by = 6
Should I be doing it in a different manner altogether, I wonder? And if so, what would be considered 'best practice'?
Thank you :)
You can add the ordering in your model:
class Post(models.Model):
...
class Meta:
ordering = ['category', '-date_posted']
See the documentation for more details:
update
Maybe its better to use custom manager for this:
from django.db import models
class CustomManager(models.Manager):
# subclass model manager
def custom_category_dict(self, **kwargs):
# add a new method for building a dictionary
nDict = dict()
for i in self.get_queryset().filter(**kwargs): # filter queryset based on keyword argument passed to this method
current_list = nDict.get(i.category.name, [])
current_list.append(i)
nDict.update({i.category.name: current_list})
return nDict
class Posts(models.Model):
# override posts model with manager
objects = CustomManager()
Usage:
# view
class PostListView(ListView):
...
def get_context_data(self, **kwargs):
context = super(PostListView, self).get_context_data(**kwargs)
context['category_wise_sorted_posts'] = Posts.objects.custom_category_dict() # you can pass filter logic as well, like Posts.objects.custom_category_dict(author_id=1)
return context
# template
{% for category, posts in category_wise_sorted_posts.items %}
<!-- Or {% for category, posts in user.posts_set.custom_category_dict.items %} -->
{{ category }}
{% for p in posts %}
{{ p.title }}
{% endfor %}
{% endfor %}

Why doesn't this query return the get_absolute_url? But another one does

I'm trying to learn why this query renders an empty Some project in my template. Am I missing the pk?
from django import template
from architecture.models import Architecture
register = template.Library()
#register.inclusion_tag('cubo/winnings.html')
def winnings():
winnings = Architecture.objects.values('year', 'project').order_by('year').filter(won=True)
return {'winnings': winnings}
But this query do work.
#register.inclusion_tag('cubo/winnings.html')
def winnings():
winnings = Architecture.objects.filter(won=True).order_by('-year')
return {'winnings': winnings}
the template: cubo/winnings.html
<ul>
{% for win in winnings|dictsortreversed:"year" %}
<li>
{{ win.year|date:"Y" }} - {{ win.project }}
</li>
{% endfor %}
</ul>
And for reference here is the models.py
from autoslug.fields import AutoSlugField
from django.db import models
from django.urls import reverse
class Architecture(models.Model):
live = models.BooleanField(default=False)
won = models.BooleanField(default=False)
project = models.CharField(max_length=200, null=True)
year = models.DateField(null=True)
description = models.TextField(null=True)
typology = models.ManyToManyField(Typology)
slug = AutoSlugField(populate_from='project', max_length=200)
def __str__(self):
return self.project
def get_absolute_url(self):
return reverse('architecture-detail', args=[str(self.slug)])
from the Queryset.values documentation, you can see that when values is used, the results come in the form of dictionaries instead of model instances:
Returns a QuerySet that returns dictionaries, rather than model instances, when used as an iterable.
Your template is probably failing when trying to call the get_absolute_url method on a dictionary.
It is probably possible to work around using annotate. Something like this (not tested):
winnings = (Architecture.objects
.annotate(url=reverse('architecture-detail',
args=[str(F('slug')])
.values('year', 'project', 'url')
.order_by('year')
.filter(won=True))
(see query expressions documentation for more detail about the F class)
and editing the template like this:
{{ win.year|date:"Y" }} - {{ win.project }}

Listing ForeignKey associated instances within template (queryset within a queryset)

I have a site which catalogs local hikes, and users can log that they have been on the hike. I have a search page which contains the hikes, and one of the fields I'm trying to display is a list of all the people who have been on the hike. I've got this figured out within the individual detail page of the hike, but can't figure out how to create a new queryset within the queryset which is printing the hikes, in order to display this info on a search page.
Here's some code:
models.py:
class Hike(models.Model):
name = models.CharField(max_length=255, unique=True)
slug = models.SlugField(unique=True)
...
class UserLog(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
hike = models.ForeignKey(Hike, on_delete=models.CASCADE)
views.py:
def hike_list(request):
qs = Hike.objects.all()
... some other filters here
?-->users = UserLog.objects.filter(id=qs.id)
template:
{% for qs in qs %}
{{ hike.name }}{{ hike.other_details_and_stuff }}
?----> {% for users in hikes %}{{ user.name }}{% endfor %}
{% endfor %}
Here's the working code within the individual hike's detail page:
views.py:
def hike_detail (request, slug)
users = UserLog.objects.filter(hike__slug=slug)
How do I call on the slug from each individual item in the queryset, then run a queryset on that?
The easiest is to add a ManyToManyField to Hike:
class Hike(models.Model):
...
users = models.ManyToManyField(User, through='app.UserLog')
If you have no extra fields in UserLog, you can even remove the UserLog model and the through parameter alltogether. In the template you can do:
{% for hike in qs %}
{{ hike.name }}{{ hike.other_details_and_stuff }}
{% for user in hike.users.all %}{{ user.name }}{% endfor %}
{% endfor %}
In order avoid too many queries, you should prefetch the users in the Hike query in the view:
qs = Hike.objects.all().prefetch_related('users')
Without the ManyToManyField, you could add a property and user the same template, but the prefetch clause could not be used that easily:
class Hike(models.Model):
...
#property
def users(self):
return User.objects.filter(userlog__hike=self)

How do I display elements in a template that have foreignkey relationships?

I am trying to display the video urls for each of the videos a user has saved as part of a playlist. The user is able to save multiple playlists as well (the first line in the view displays all of the playlists). I am struggling to figure out how to show the videos in each of the playlists though. Any advice?
views.py
def profile(request):
playlist = UserPlaylist.objects.filter(profile=request.user)
return render_to_response('reserve/templates/profiles.html', {'playlist':playlist},
context_instance=RequestContext(request))
models.py
class Playlist(models.Model):
playlist = models.CharField('Playlist', max_length = 2000, null=True, blank=True)
def __unicode__(self):
return self.playlist
class Video(models.Model):
video_url = models.URLField('Link to video', max_length = 200, null=True, blank=True)
def __unicode__(self):
return self.video_url
class UserPlaylist(models.Model):
profile = models.ForeignKey(User)
playlist = models.ForeignKey(Playlist)
def __unicode__(self):
return unicode(self.playlist)
class Videoplaylist(models.Model):
video = models.ForeignKey(Video)
playlist = models.ForeignKey(UserPlaylist)
def __unicode__(self):
return unicode(self.playlist)
template: profiles.html
{% for feed in playlist %}
{{feed}}
<br>
{% endfor %}
Foreign key relationships can be accessed using . to span relationship
{{ feed.playlist.playlist }}
{{ feed.profile.username }}
since this is a queryset of UserPlaylist objects they have either a profile or playlist property.
Be careful though! I do believe this does a seperate query each time you access a foreign relationship. I'm not sure though but it is worth checking out on debug toolbar or something.
according to Victor 'Chris' Cabral you can span relationships backwards using
[model_you_want_to_span]_set.all
you coudl also do this lookup in your view using
vpls = Videoplaylist.objects.filter(playlist__profile=request.user)
{% for feed in playlist %}
{{feed}}
{% for vpl in feed.videoplaylist_set.all %}
{{ vpl.video.video_url }}
{% endfor %}
<br>
{% endfor %}

Categories