Django prefetch related no duplicates with intermediate table - django

I have a question I am trying to solve for one day now.
With the models
class Quote(models.Model):
text = models.TextField()
source = models.ForeignKey(Source)
tags = models.ManyToManyField(Tag)
...
class Source(models.Model):
title = models.CharField(max_length=100)
...
class Tag(models.Model):
name = models.CharField(max_length=30,unique=True)
slug = models.SlugField(max_length=40,unique=True)
...
I am trying to model the world of quotes. with relationships: one Source having many Quotes, one Quote having many Tags.
Problem is:
How do I get all Tags that are contained in a Source (through the contained Quotes) ?
with the minimum possible queries.
with the amount of times they are contained in that source
I have tried the naive one with no prefetch related, with a model method
def source_tags(self):
tags = Tag.objects.filter(quote__source__id=self.id).distinct().annotate(usage_count=Count('quote'))
return sorted(tags, key=lambda tag:-tag.usage_count)
And in the template:
{% for tag in source.source_tags|slice:":5" %}
source.quote
{% endfor %}
Now I have
sources = Source.objects.all().prefetch_related('quote_set__tags')
And in the template I have no idea how to iterate correctly to get the Tags for one source, and how I would go about counting them instead of listing duplicate tags.

This will get the result in a single SQL query:
# views.py
from django.db.models import Count
from .models import Source
def get_tag_count():
"""
Returns the count of tags associated with each source
"""
sources = Source.objects.annotate(tag_count=Count('quote__tags')) \
.values('title', 'quote__tags__name', 'tag_count') \
.order_by('title')
# Groupe the results as
# {source: {tag: count}}
grouped = {}
for source in sources:
title = source['title']
tag = source['quote__tags__name']
count = source['tag_count']
if not title in grouped:
grouped[title] = {}
grouped[title][tag] = count
return grouped
# in template.html
{% for source, tags in sources.items %}
<h3>{{ source }}</h3>
{% for tag, count in tags.items %}
{% if tag %}
<p>{{ tag }} : {{ count }}</p>
{% endif %}
{% endfor %}
{% endfor %}
Complementary tests :)
# tests.py
from django.test import TestCase
from .models import Source, Tag, Quote
from .views import get_tag_count
class SourceTags(TestCase):
def setUp(self):
abc = Source.objects.create(title='ABC')
xyz = Source.objects.create(title='XYZ')
inspire = Tag.objects.create(name='Inspire', slug='inspire')
lol = Tag.objects.create(name='lol', slug='lol')
q1 = Quote.objects.create(text='I am inspired foo', source=abc)
q2 = Quote.objects.create(text='I am inspired bar', source=abc)
q3 = Quote.objects.create(text='I am lol bar', source=abc)
q1.tags = [inspire]
q2.tags = [inspire]
q3.tags = [inspire, lol]
q1.save(), q2.save(), q3.save()
def test_count(self):
# Ensure that only 1 SQL query is done
with self.assertNumQueries(1):
sources = get_tag_count()
self.assertEqual(sources['ABC']['Inspire'], 3)
self.assertEqual(sources['ABC']['lol'], 1)
I have basically used the annotate and values functions from the ORM. They are very powerful because they automatically perform the joins. They are also very efficient because they hit the database only once, and return only those fields which are specified.

Related

How to iterate in subitems of a object in a django template

views.py:
from django.views import generic
from .models import Servico
class ServicoView(generic.DetailView):
model = Servico
context_object_name = 'servico'
template_name = 'servico.html'
models.py:
from djongo import models
class PublicoAlvo(models.Model):
def __str__(self):
return ''
alvo1 = models.CharField(max_length = 126)
alvo2 = models.CharField(max_length = 126, blank = True, default = '')
class Meta:
abstract = True
class Servico(models.Model):
t_id = models.CharField(primary_key = True, unique = True, max_length = 252)
alvos = models.EmbeddedField(
model_container = PublicoAlvo
)
urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('servicos/<slug:pk>/', views.ServicoView.as_view(), name = 'servico')
]
I think these are the relevant files in my Django app folder. Back to the question, how can I iterate over the values that are going to be stored in servico.alvos in my template? If I want to show t_id, I just use {{ servico.t_id }} and it works fine. I could write something like:
<HTML1>
{{ servico.alvos.alvo1 }}
<HTML2>
<HTML1>
{{ servico.alvos.alvo2 }}
<HTML2>
And that would show the values that I want, but would make things uglier, since I would have to write a lot of repeated standard HTML (that I indicated as and ) to format each value inside servico.alvos, and more limited (imagine if I decide to change the model and add more 6 values in the PublicoAlvo class). I tried the following:
{% for alvo in servico.alvos.all %}
<HTML1>
{{ alvo }}
<HTML2>
{% endfor %}
and
{% for alvo in servico.alvos.items %}
<HTML1>
{{ alvo }}
<HTML2>
{% endfor %}
But I get nothing printed. When I try:
{% for alvo in servico.alvos %}
<HTML1>
{{ alvo }}
<HTML2>
{% endfor %}
I get 'PublicoAlvo' object is not iterable
Is there a way to get what I want using a loop in my template or changing something in my models.py?
Try
{{ servico.t_id }}
{{ servico.alvos }}
Then in your models.py
class PublicoAlvo(models.Model):
def __str__(self):
# Option 1: List all <alvo> fields
return ", ".join(
[
self.alvo1,
self.alvo2,
]
)
# Option 2: If you don't like manually listing each <alvo> field
# return ", ".join(
# [
# getattr(self, f"alvo{index}") for index in range(1, 3)
# ]
# )
...
This might give something like
The value for alvo1, While for alvo2 is this one
Update
You could also try
{% for key, value in servico.alvos.items %}
{% if key|slice:":4" == "alvo" %}
{{ value }}<br>
{% endif %}
{% endfor %}
This might show something like
The value for alvo1
While for alvo2 is this one
Based on the first answer of Niel Godfrey Ponciano, I was able to solve the problem.
models.py:
from djongo import models
class PublicoAlvo(models.Model):
def __str__(self):
return ''
def list(self):
return [value for key, value in self.__dict__.items() if not key.startswith('_')]
alvo1 = models.CharField(max_length = 126)
alvo2 = models.CharField(max_length = 126, blank = True, default = '')
class Meta:
abstract = True
class Servico(models.Model):
t_id = models.CharField(primary_key = True, unique = True, max_length = 252)
alvos = models.EmbeddedField(
model_container = PublicoAlvo
)
And then I can iterate over servico.alvos.list using a for in the template just by adding the list method that returns the relevant fields (variables) values in my class.

Queryset only allowing me to SELECT just one tables fields in a join

I have two tables, but am just trying to get just the dNm from T table (while joining), but instead I can only pull fields from TSF.
I have models file:
models.py
class T(models.Model):
emailVerified = models.EmailField(max_length=50)
dNm = models.CharField(max_length=40,unique=True)
FKToUser = models.ForeignKey('auth.user', default=None, on_delete=models.PROTECT)
class TSF(models.Model):
httpResponse = models.IntegerField(validators=[MaxValueValidator(3)])
FKToT = models.ForeignKey('T', on_delete=models.PROTECT)
In regular (pseudo) sql I'm trying to do something like:
SELECT dNm
FROM T, TSF
WHERE T.id=TSF.FKToT
AND T.FKToUser=<<THE CURRENTLY SIGNED IN USER>>
However, its only allowing me to do the following in pseudo sql:
SELECT <any field from TSF>
FROM T, TSF
WHERE T.id=TSF.FKToT
AND T.FKToUser=<<THE CURRENTLY SIGNED IN USER>>
My views.py:
def viewed(request):
AUS = TSF.objects.filter(FKToTld__FKToUser=request.user).values('dNm')
return render(request, 'file.html', {
'ATFS':ATFSs
})
Outputting in template
{{ t.dNm }}
UPDATE This is now not throwing an error on page, but sending a bunch of blanks.
What am I doing wrong here?
values() returns a Queryset of dictionaries. you can check here for more info.
You can do something like this:
views.py
AUS = TSF.objects.filter(FKToTld__FKToUser=request.user).values('FKToTld__dNm').disctinct()
template.html
{% for key, value in ATFS.items %}
{{ key }}: {{ value }}
{% endfor %}

How to target a specific object with Django templates

I have a class called Features in my models.py. In my html, I am displaying a list on the right that excludes two of these Features, one is the active feature that has been selected, the other is the most recently added since they are the main content of my page. The remaining Features in the list are displayed by date and do show what I am expecting.
Now, I want to single out the first, second and third Features (title only) in THAT list so I can place them in their own separate divs - because each has unique css styling. There are probably numerous ways of doing this, but I can't seem to figure any of them out.
This is a link to my project to give a better idea of what I want (basically trying to get the content in those colored boxes on the right.)
I'm just learning Django (and Python really), so thanks for your patience and help!
HTML
{% for f in past_features %}
{% if f.title != selected_feature.title %}
{% if f.title != latest_feature.title %}
<h1>{{ f.title }}</h1>
{% endif %}
{% endif %}
{% endfor %}
VIEWS
def feature_detail(request, pk):
selected_feature = get_object_or_404(Feature, pk=pk)
latest_feature = Feature.objects.order_by('-id')[0]
past_features = Feature.objects.order_by('-pub_date')
test = Feature.objects.last()
context = {'selected_feature': selected_feature,
'latest_feature': latest_feature,
'past_features': past_features,
'test': test}
return render(request, 'gp/feature_detail.html', context)
MODELS
class Feature(models.Model):
title = models.CharField(db_index=True, max_length=100, default='')
content = models.TextField(default='')
pub_date = models.DateTimeField(db_index=True, default=datetime.now, blank=True)
def __str__(self):
return self.title
def __iter__(self):
return [
self.id,
self.title ]
You can either store the first three Features in separate variables in your context or add checks to your template loop like {% if forloop.first %} or {% if forloop.counter == 2 %}.
If all you want is to not have the
selected_feature
latest_feature
these two records out of the past_features queryset, then you can use exclude on the past_features query and pass the id's of the selected_features and latest_feature objects.
The views.py would look like:
def feature_detail(request, pk):
selected_feature = get_object_or_404(Feature, pk=pk)
latest_feature = Feature.objects.order_by('-id')[0]
# Collect all the id's present in the latest_feature
excluded_ids = [record.pk for record in latest_feature]
excluded_ids.append(selected_feature.pk)
#This would only return the objects excluding the id present in the list
past_features = Feature.objects.order_by('-pub_date').exclude(id__in=excluded_ids)
test = Feature.objects.last()
context = {'selected_feature': selected_feature,
'latest_feature': latest_feature,
'past_features': past_features,
'test': test}
return render(request, 'gp/feature_detail.html', context)
Django provides a rich ORM and well documented, go through the Queryset options for further information.
For access to a specific object in Django templates see following example:
For access to first object you can use {{ students.0 }}
For access to second object you can use {{ students.1 }}
For access to a specific field for example firstname in object 4 you can use {{ students.3.firstname }}
For access to image field in second object you can use {{ students.1.photo.url }}
For access to id in first object you can use {{ students.0.id }}

Django filtering and querying: do it in views.py, template, or filters?

So I am working on a small Django project, which for the moment doesn't require optimization. But to prepare for the future, I'd like to know a bit more about the three approaches.
For instances, as part of the models, I have User and UserProfile, Transaction.
class User(models.Model):
name = ...
email = ...
class UserProfile(models.Model):
user = models.ForeignKey(User, related_name='profile')
photo = models.URLField(...)
...
class Transaction(models.Model):
giver = models.ForeignKey(User, related_name="transactions_as_giver")
receiver = models.ForeignKey(User, related_name='transactions_as_receiver')
...
I frequently need to do something like "return transactions that the request.user is giver or receiver", or "return the profile photo of a user". I have several choices, for instance to get a list of pending transactions and photos of both parties, I can do it at views.py level:
1.
#views.py
transactions = Transaction.objects.filter(Q(giver=request.user)|Q(receiver=request.user))
for transaction in transactions:
giver_photo = transactions.giver.profile.all()[0].photo
# or first query UserProfile by
# giver_profile = UserProfile.objects.get(user=transaction.giver),
# then giver_photo = giver_profile.photo
#
# Then same thing for receiver_photo
transaction['giver_photo'] = giver_photo
...
Or I can do it more on template level:
# some template
<!-- First receive transactions from views.py without photo data -->
{% for t in transactions %}
{{t.giver.profile.all.0.photo}}, ...
{% endfor %}
Or I can move some or even all of the above stuffs into filters.py
# some template
{{ for t in request.user|pending_transactions }}
{{ t.giver|photo }} {{ t.receiver|photo }}
{{ endfor }}
where photo and pending_transactions are roughly the same code in original views.py but moved to a filter.
So I wonder is there a best practice/guide line on how to choose which approach?
From Django documentation, lower level is faster, and therefore 2. 3. should be slower than 1; but how about comparing the 2. and 3.?
In getting a user photo, which of the two should be recommended, transactions.giver.profile.all()[0].photo OR profile = UserProfile.objects.get(...) --> photo = profile.photo?
Move this logic into models and managers. Views and templates must be as short as possible.
class User(models.Model):
...
def transactions(self):
return Transaction.objects.filter(Q(giver=self)|Q(receiver=self))
def photo(self):
return self.profile.all().first().photo
So the template will be:
{% for t in request.user.transactions %}
{{ t.giver.photo }} {{ t.receiver.photo }}
{% endfor %}
My experience says that business logic in model as much easier to test, support and reuse than in the views/templates.

Django tagging select_related

I using Django tagging project.
That is was very stable project.
Working on django 1.3.
But i have a problem.
# in models.py
from tagging.fields import TagField
class Blog(models.Model):
title = models.CharField(max_length = 300)
content = models.TextField()
tags = TagField()
author = models.ForeignKey(User)
# in views.py
def blog_list(request):
# I Want to use select related with tags
blogs = Blog.objects.all().select_related("user", "tags") # ????
....
# in Templates
{% load tagging_tags %}
{% for blog in blogs %}
{% tags_for_object blog as tags %}
{{blog.title}}
{% for tag in tags %}
{{tag}}
{% endfor %}
{% endfor %}
django-tagging uses a generic foreign key to your model, so you can't just use select_related.
Something like this should do the trick though:
from django.contrib.contenttypes.models import ContentType
from collections import defaultdict
from tagging.models import TaggedItem
def populate_tags_for_queryset(queryset):
ctype = ContentType.objects.get_for_model(queryset.model)
tagitems = TaggedItem.objects.filter(
content_type=ctype,
object_id__in=queryset.values_list('pk', flat=True),
)
tagitems = tagitems.select_related('tag')
tags_map = defaultdict(list)
for tagitem in tagitems:
tags_map[tagitem.object_id].append(tagitem.tag)
for obj in queryset:
obj.cached_tags = tags_map[obj.pk]
I have recently encountered the same problem and solved it without a single db query. Indeed, we don't need tag id to get url. As tag name is unique and is db_index you can get url using name field instead of id. E.g.
# your_app/urls.py
url(r'tag/(?P<tag_name>[-\w]+)$', tag_detail_view, name='tag_detail')
Also, tagging TagField gives us a string with tag names, like 'python,django'. So we can write a custom template filter:
# your_app/templatetags/custom_tags.py
from django.urls import reverse
#register.filter
def make_tag_links(tags_str):
return ', '.join([u'%s' % (reverse(
'tag_detail', args=[x]), x) for x in tags_str.split(',')])
And then you can write in template:
# your_list_template.html
{% for blog in blogs %}
{{blog.title}}
{% if blog.tags %}
{{ blog.tags|make_tag_links|safe }}
{% endif %}
{% endfor %}