Creating a context object with values from several related models - python

I can't seem to figure this out. My view takes an argument for Team.id, and I want to return a context object with each User object with a certain value in User.profile.team and the associated date from Reports. I feel like I started on the right track, but am missing something. The output of my template contains all the data I'm trying to get, but not in a way that can be displayed logically.
Basically, I'm using models similar to the following:
class Reports(models.Model):
user = models.ForeignKey(User, null=True, on_delete=models.PROTECT)
product = models.CharField(max_length=15)
apps_activated = models.IntegerField(blank=True, null=True)
prem_submitted = models.DecimalField(max_digits=30, decimal_places=2)
class Team(models.Model):
name = models.CharField(max_length=255)
leader = models.ForeignKey(User,on_delete=models.PROTECT)
Extended user profile:
class Profile(models.Model):
COORDINATOR = 1
LEADER = 2
ADMIN = 3
ROLE_CHOICES = (
(COORDINATOR, 'Coordinator'),
(LEADER, 'Leader'),
(ADMIN, 'Admin'),
)
user = models.OneToOneField(User, on_delete=models.CASCADE)
team = models.ForeignKey(Team, on_delete=models.PROTECT,null=True)
role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, null=True, blank=True)
The closest I got in my view to return the desired data is with the following:
team = 1
team_name = Team.objects.get(id=team)
team_users = User.objects.filter(profile__team=team).all()
team_stats = []
for user in team_users:
team_stats.append(Reports.objects.filter(user_id=user.id))
With a template that looks like:
{% block content %}
<h1>{{ team }}</h1>
<ul>
{% for user in team_users %}
<li>{{ user.first_name }} {{ user.last_name }}</li>
{% endfor %}
</ul>
<ul>
{% for stat in team_stats %}
<li>
{% for line in stat %}
{{ line.product }} {{ line.type }} #etc #etc
{% endfor %}
</li>
{% endfor %}
</ul>
{% endblock %}
I thought I was on to something with prefetch_related(), but couldn't figure it out. Ideally, I'd only have to return one context object to my template.
Edit:
If it makes it more clear, this query returns the results I'm attempting to pass to the template:
select auth_user.first_name, auth_user.last_name, r.product, r.apps_activated, r.prem_submitted, r.conversion_percentage, r.type
from auth_user
join home_profile
on auth_user.id = home_profile.user_id
join reports_reports as r
on auth_user.id = r.user_id
where home_profile.team_id = 1
which returns rows that look like:
first_name-last_name-product-apps_activated-prem_submitted-conversion_rate-type
user_1-user_1_last-product_1-693-139764.00-53.86-type1
user_1-user_1_last-product_2-74-27400.10-0.00-type1
user_1-user_1_last-product_3-102-19782.00-47.00-type2
user_2-user_2_last-product_1-7-2437.70-0.00-type2
user_2-user_2_last-product_2-52-10608.00-42.54-type3
user_2-user_2_last-product_3-260.40-0.00-type3

Potential Solution 1:
So, I would do the following. Change user in your Reports model to link explicitly to Profile (which, in turn, links to the user)
class Reports(models.Model):
profile = models.ForeignKey(Profile, null=True, on_delete=models.PROTECT)
product = models.CharField(max_length=15)
apps_activated = models.IntegerField(blank=True, null=True)
prem_submitted = models.DecimalField(max_digits=30, decimal_places=2)
It seems likely that you are looking for a are probably looking for something like a select_related() query, adding in an extra ForeignKey field for the Profile:
reports = Reports.objects.select_related('profile')
You can check the resulting SQL via str(reports.query), which should result in sth along the lines of what you outlined in your question.
The returned cursor values are then translated into the appropriate ORM model instances, so that when you loop over these reports, you access the related tables' values via their own objects. However, these accesses along the pre-selected forward relations will not cause extra db hits:
{% for report in reports %}
{{ report.profile.user.username }}
{{ report.product }}
# ...
{% endfor %}
Let me know how you get on, and if we can brainstorm a solution more suitable, if this one isn't.
Potential Solution 2:
Perhaps another solution, and maybe the simplest of the two, would be to have a ManyToMany relationship in your Profile model to Reports:
class Profile(models.Model):
COORDINATOR = 1
LEADER = 2
ADMIN = 3
ROLE_CHOICES = (
(COORDINATOR, 'Coordinator'),
(LEADER, 'Leader'),
(ADMIN, 'Admin'),
)
user = models.OneToOneField(User, on_delete=models.CASCADE)
team = models.ForeignKey(Team, on_delete=models.PROTECT,null=True)
role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, null=True, blank=True)
reports = models.ManyToManyField(Reports, ...)
Then you should be able to loop over users in the template:
{% for user in users %}
{% for report in user.reports.all %}
{{ report.product }}
{% endfor %}
{% endfor %}

Related

Retrieve a filtered ForeignKey with Django

I have three main models, Picture, Place and PlaceRating:
class Picture(models.Model):
file = ImageField(max_length=500, upload_to="images")
user = models.ForeignKey(User, null=True, related_name="userpictures")
place = models.ForeignKey(Place, null=True, related_name='pictures')
class PlaceRating(models.Model):
place = models.ForeignKey(Place, null=True, related_name="placeratings")
user = models.ForeignKey(User, null=True, related_name="userratings")
rating = models.DecimalField(null=True, max_digits=4, decimal_places=1)
class Place(models.Model):
name = CharField(max_length=50)
I would like to display the place's rating given by the user, together with the place's image, but I cannot manage to do that as I would need to filter the ForeignKey and Django does not seem to allow that.
Example of what I would like to do:
View:
pictures = Picture.objects.filter(user=request.user)
Template:
{% for picture in pictures %}
<img src="{{ picture.file.url }}" class="bigpicture">
{{ picture.place.placeratings.0.rating|user:picture.user }}
{% endfor %}
For information, I managed to do it with templatetags, but this generates a lot of different queries to the database which I can't prefetch..:
{% for picture in pictures %}
<img src="{{ picture.file.url }}">
{% getplaceratingrelatedtopic picture.place.id picture.user.id %}
{% endfor %}
And:
#register.simple_tag
def getplaceratingrelatedtopic(placeid, userid):
print(placeid)
theplace = Place.objects.get(id=placeid)
user = User.objects.get(id=userid)
placerating = PlaceRating.objects.filter(author=user, place=place).last()
if rating:
return placerating.rating
else:
return ""
I work with Python 2.7/Django 1.9.
Any clue ? Thanks a lot!
You should move the logic to the model and use it in the template.
class Picture(models.Model):
file = ImageField(max_length=500, upload_to="images")
user = models.ForeignKey(User, null=True, related_name="userpictures")
place = models.ForeignKey(Place, null=True, related_name='pictures')
class PlaceRating(models.Model):
place = models.ForeignKey(Place, null=True, related_name="placeratings")
user = models.ForeignKey(User, null=True, related_name="userratings")
rating = models.DecimalField(null=True, max_digits=4, decimal_places=1)
class Place(models.Model):
name = CharField(max_length=50)
#property
def rating(self):
return self.placeratings.get().rating
{% for picture in picture_list %}
<img src="{{ picture.file.url }}" class="bigpicture">
{{ picture.place.rating }}: {{ picture.user.username }}
{% endfor %}

Indexing and searching related objects with haystack

I'm pretty new to search implementation, bear with me while I'm learning!
So my pet project is a recipe site and each recipe can have n steps. the model looks something like:
class Recipe(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
hotness = models.ForeignKey(Hotness)
recipe_diet = models.ManyToManyField(DietType)
ingredients = models.ManyToManyField(Ingredient, through="RecipeIngredient")
class DietType(models.Model):
diet_type = models.CharField(max_length=50)
description = models.TextField(null=True, blank=True)
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe)
ingredient = models.ForeignKey(Ingredient)
quantifier = models.ForeignKey(Quantifier)
quantity = models.FloatField()
class RecipeSteps(models.Model):
step_number = models.IntegerField()
description = models.TextField()
recipe = models.ForeignKey(Recipe)
(shortened for brevity)
I want to index all of it: Recipe, RecipeIngredient, DietType and Steps...
The DietType and RecipeIngredient seem to be working fine, but the Steps are not. I assume this has to do with the usage of 'RelatedSearchQuerySet' ?
Here is my search_indexes.py:
from haystack import indexes
from recipes.models import Recipe
class RecipeIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
title = indexes.CharField(model_attr='title')
ingredients = indexes.MultiValueField(indexed=True, stored=True)
description = indexes.CharField(model_attr='description')
hotness = indexes.CharField(model_attr='hotness')
diet_type = indexes.MultiValueField(indexed=True, stored=True)
recipesteps = indexes.MultiValueField(indexed=True, stored=True)
def prepare_steps(self, object):
return [step.description for step in object.recipesteps.all()]
def get_model(self):
return Recipe
def load_all_queryset(self):
# Pull all objects related to the Note in search results.
return Recipe.objects.all().select_related()
Here is the template recipe_text.txt:
{{ object.title }}
{{ object.cuisine }}
{% for ingr in object.ingredients.all %}
{{ ingr.title }}
{% endfor %}
{{ object.description }}
{% for dt in object.recipe_diet.all %}
{{ dt.diet_type }}
{% endfor %}
{{ object.user }}
{{ object.hotness }}
{% for step in object.recipesteps.all %}
{{ step.description }}
{% endfor %}
{{ object.body }}
I can search ingredients, title, description, diet type - everything works, except the RecipeSteps.
Finally, I'm making queries through the shell only at the moment:
#producing results:
sq = SearchQuerySet().filter(content='onion') #ingredient
sq = SearchQuerySet().filter(content='bolognese') #title
sq = SearchQuerySet().filter(content='bologna') #description
#not producing any results:
sq = SearchQuerySet().filter(content='chop') #step
sq = RelatedSearchQuerySet().filter(content='chop').load_all() #assuming this does the expanded search
Any idea?
I have identified two issues:
The name prepare_steps in your RecipeIndex is wrong it should be prepare_{field_name} so change it to prepare_recipesteps
You are trying to access related steps object.recipesteps.all objects recipe_text.txt in a wrong way, it should be object.recipesteps_set.all. Or keep using recipesteps but add this as a related_name in RecipeSteps model for ForeignKey Recipe e.g.
class RecipeSteps(models.Model):
# //
recipe = models.ForeignKey(Recipe, related_name='recipesteps')

Foreign key not rendering in template

I'm building a commenting system, which is working fine but i'm now trying to integrate voting. So I made another model to handle that and I tried to pair it using ForeignKey. Not too familiar with ForeignKey but i've looked at some other answers here to see how to render it in the template. I tried that using the nested for loop in my template below but {{ j.user }} doesn't render anything. Can anyone tell me what I'm doing wrong?
models.py
class Comment(models.Model):
destination = models.CharField(default='1', max_length=12, blank=True)
author = models.CharField(max_length=120, blank=True)
comment_id = models.IntegerField(default=1)
parent_id = models.IntegerField(default=0)
comment_text = models.TextField(max_length=350, blank=True)
timestamp = models.DateTimeField(default=timezone.now, blank=True)
def __str__(self):
return self.comment_text
class CommentScore(models.Model):
user = models.ForeignKey(User)
comment = models.ForeignKey(Comment)
upvotes = models.IntegerField(default=0)
downvotes = models.IntegerField(default=0)
views.py
...
comment_list = Comment.objects.filter(destination=id)
score = CommentScore.objects.all()
context = {
'score': score,
'comment_list': comment_list,
}
return render(request, 'article.html', context)
template
{% for i in comment_list %}
<div class='comment_div'>
<h3>{{ i.author }}</h3>
{% for j in comment_list.score_set.all %}
{{ j.user }} #nothing comes up
{% endfor %}
<p>{{ i.comment_text }}</p>
</div>
{% endfor %}
when using _set, the reverse relationship lookup, you must provide the full model name, you must also specify which instance this list of related models this "set" is for so it should be
{% for j in i.commentscore_set.all %}
{% empty %}
No scores found
{% endfor %}
You may also wish to set a related_name for the foreign key
comment = models.ForeignKey(Comment, related_name='scores')
...
{% for j in i.scores.all %}

Nested loop in a dictionnary template django

In my view.py, my context look like this:
context = {'books': books, 'asked_author': asked_author, 'role': role_field_list}
books is a list of dictionary (a list of book) and for each book, it exists one or more keys for which the key is named by a name in role_field_list:
I try to execute this template:
{% for book in books %}
{% for name in role %}
<h1>{{name}}</h1>
{% for authors in book.name %}
{{ authors.lastname }} {{authors.firstname}}
{% endfor %}
{% endfor %}
{% endfor %}
But book.name doesn't worked.
It takes name as a litteral 'name' not as a variable...
Thank you for any help.
To have more details about "field" and "type" of my context dictionnary, this my view.py:
def booksAuthor(request, author):
books_role = AuthorRoleBook.objects.filter(author_book=author).values() # get all the books written by one author
asked_author = AuthorBook.objects.get(pk=author) # get lastname and firstname for the author selected
books=[]
for book_role in books_role:
book = Book.objects.get(pk=book_role['book_id'])
book_dict = model_to_dict(book)
authors_role = AuthorRoleBook.objects.filter(book_id=book).values() # Get id of the different contributor for each book
role_field_list = ['auteur', 'traducteur', 'editeur_scientifique', 'directeur_publication']
for name in role_field_list:
list_author=[]
for author_role in authors_role:
if author_role['role_id']==name:
author=AuthorBook.objects.get(pk=author_role['author_book_id'])
author_dict = model_to_dict(author)
list_author.append(author_dict)
else:
pass
book_dict[name]=list_author
books.append(book_dict)
print(book_dict)
context = {'books': books, 'asked_author': asked_author, 'role': role_field_list}
return render(request, 'books_author.html', context)
And for my models, maybe it is not the easier way but this is:
class AuthorRoleBook(models.Model):
author_book = models.ForeignKey(AuthorBook)
role = models.ForeignKey('Role')
book = models.ForeignKey('Book')
class AuthorBook(models.Model):
lastname = models.CharField(max_length=100, blank=True, null=True)
firstname = models.CharField(max_length=100, blank=True, null=True)
.... ....
unique_together = (('lastname', 'firstname'),)
class Role(models.Model):
id = models.CharField(primary_key=True, max_length=100)
class Book(models.Model):
titre = models.TextField(blank=True, null=True)
....... .... ...
isbn_electronique = models.CharField(max_length=250, blank=True, null=True)
Thank you in advance.
For me it is working now. I keep the same views.py but in my template I do this:
{% for book in books %}
<h4>{{ book.titre }}</h4>
{% if book.directeur_publication %}
Directeur Publication:
{% for name in book.directeur_publication %}
{{ name.lastname }} {{name.firstname}}
{% endfor %}
{% endif %}
{% if book.editeur_scientifique %} ....
...
.....Etc
And for each value in role_field_list (['auteur', 'traducteur', 'editeur_scientifique', 'directeur_publication']) I put manually the value n my template in a "if condition".
It is not really a beautiful way... but it is working. For sure to use model object and not dictionary will be better.
Thanks for your answer, nevertheless if you have a smart code or idea to do that...

Complex django query in a many to many relationship

I have the following models
class Book(models.Model):
name = models.CharField(max_length=140)
class UserProfile(models.Model):
favorites = models.ManyToManyField(Book, null=True, blank=True)
user = models.OneToOneField(User)
I need to craete a list of all books and show which ones are favorites and which ones are not.
I need a queryset for a view that gets me all the books like
Book.objects.all()
but i also need to know for each book if it is a favorite for that user and then pass this queryset to the template.
Thanks.
This is a relatively straightforward use of the ManyToManyField.
class Book(models.Model):
name = models.CharField(max_length=140)
class UserProfile(models.Model):
favorites = models.ManyToManyField(Book, null=True, blank=True)
user = models.OneToOneField(User)
favorite_books = this_user_profile.favorites.all()
for b in Book.objects.all():
if b in favorite_books:
print "Book", b.name, "is a favorite of this user!"
else:
print "Book", b.name, "is not a favorite of this user!"
ETA: Since you say you want to add it to the template, give it to the template as a list of tuples.
book_list = [(b, (b in favorite_books)) for b in Book.objects.all()]
In your template, have the code
{% for book, is_favorite in book_list %}
{% if is_favorite %}
{% comment %} Do something with this favorite book {% endcomment %}
{% else %}
{% comment %} Do something with this non favorite book {% endcomment %}
{% endif %}
{% endfor %}

Categories