I have this models (simplified):
#models.py
class Expression(models.Model):
text = models.CharField(max_length=254)
class Country(models.Model):
name = models.CharField(max_length=100)
class Definition(models.Model):
expression = models.ForeignKey(Expression)
country = models.ForeignKey(Country)
text = models.CharField(max_length=254)
class Vote(models.Model):
definition = models.ForeignKey(Definition)
And this view
#views.py
def index(request):
expressions = Expression.objects.all()
return render(request, 'expression_index.html', { 'expressions':expressions)
So it will show the last 10 created expressions.
Then in the template I have this:
#index.html
{% for expression in expressions %}
{{ expression }}
{% for definition in expression.definition_set.all %}
<ul>
<li>{{ definition }}</li>
</ul>
{% endfor %}
{% endfor %}
Every definition has several votes.
Every vote is a single row so we can do:
definition.votes_set.count()
How can I achieve to display them like this:
The top definition of every country alphabetically. Each country appears only with one definition.
Lets say Germany has two definitions for expression "A" and Denmark has three definitions for the same expression it will show only two definitions: the one with the most votes.
I hope I'm making sense.
Thanks
I think something like this should work (untested)
from django.db.models import Count
{{ expression.definition_set.annotate(Count('votes_set')) }}
{% for definition in expression.definition_set.order_by('country','votes_set__count') %}
This queryset will sort alphabetically by country, then each country from it's top voted definition to least.
expression = Expression.objects.get(text__exact="A")
definitions = Definition.objects.filter(expression=expression).annotate(num_votes=Count('vote')).order_by("country__name", "-num_votes")
If i try to keep only the top definition of each country as you wanted, and set .distinct("country") at the end of the queryset, it will throw this error:
NotImplementedError at annotate() + distinct(fields) is not implemented.
So another solution would be:
import operator
expression = Expression.objects.get(text__exact="A")
# Get top definitions sorted by num of votes and convert the values to list.
top_definitions = list(Definition.objects.filter(expression=expression).annotate(num_votes=Count('vote')).order_by("num_votes").values("id", "country", "country__name", "expression", "text", "num_votes"))
# Remove dublicate countries and leave the only the top voted definition.
definitions = list({v['country']: v for v in top_definitions}.values())
# Sort alphabetically by country.
definitions.sort(key=operator.itemgetter('country__name'))
return render(request, 'expression_index.html', {'definitions': definitions, 'expression': expression})
template:
<h1>{{ expression.text }}</h1>
<ul>
{% for definition in definitions %}
<li>{{ definition.country__name }} - {{ definition.text }} - {{ definition.num_votes }}</li>
{% endfor %}
</ul>
Related
In Django, I have created a dataclass to help me move data around in a standardized way:
# models.py
class Person(models.Model):
name = CharField(max_length=50)
#utils.py
#dataclass
class Performance:
person: Person
last_year: int
five_years: int
The idea here is that I can access the properties by name when transferring them to the template:
#views.py
class IndexView(View):
def get(self, request):
persons = Person.objects.filter(name="pam")
perfs_array = []
for person in persons:
p = Performance()
p.last_year = #some math
p.five_years = #more math
perfs_array.append(p)
context = {"perfs": perfs_array}
return render(...)
The goal is to be able to do this:
<!--index.html-->
{% for perf in perfs %}
<h1>{{ perf.person.name }}</h1>
<p>last year's performance: {{ p.last_year }}</p>
{% endfor %}
However, I can only access the data using numbered indices:
<!--index.html-->
{% for perf in perfs %}
<h1>{{ perf.0.name }}</h1>
<p>last year's performance: {{ p.1 }}</p>
{% endfor %}
This made it very hard to write correct code and to debug the templates when things go inevitably south. Is there a way to access the data with named indices?
Thanks!
I have 2 lists of equal size elements that I want to join and then output to my Django template.
But I'm having trouble identifying the row so just that row is printed.
I am trying to zip the two lists and then create a new list that will loop through onto the template page.
When I use the variable on the template like the code below It works fine but I want to be able to loop through the list.
I know this is incorrect as not all the elements are printed to the template but it creates the desired result.
<p> {{ news.esp_article.0 }}</p>
<p> {{ news.eng_article.0 }}</p>
<hr>
<p> {{ news.esp_article.1 }}</p>
<p> {{ news.eng_article.1 }}</p>
<hr>
<p> {{ news.esp_article.2 }}</p>
<p> {{ news.eng_article.2 }}</p>
<hr>
<p> {{ news.esp_article.3 }}</p>
<p> {{ news.eng_article.3 }}</p>
<hr>
<p> {{ news.esp_article.4 }}</p>
<p> {{ news.eng_article.4 }}</p>
To try to solve it here is my views.py And I'm almost certain my problem is my queryset result.
class ArticleViewPage(DetailView):
model = Newspapers
template_name = 'rtves/article.html'
context_object_name = 'news'
eng_articles = Newspapers.objects.values_list('eng_article')
esp_article = Newspapers.objects.values_list('esp_article')
zip_scripts = list(zip(eng_article, esp_article))
context.update(
zip_scripts = zip_scripts
)
Then I get the full raw data of every row in the database
here is my template file:
{% for text1, text2 in zip_scripts %}
<p>{{ text1 }}</p>
<p>{{ text2 }}</p>
{% endfor %}
Here is my URL pattern file which is working correctly
path('newspapers/articles/<slug:pk>/', ArticleViewPage.as_view(), name='Articles'),
I know the problem is my queryset and it's pulling in too much detail but I have tried filtering and about 20 other things
here is my models both objects I'm trying to print to the template are saved ass ArrayFields
class Newspapers(models.Model):
esp_article = ArrayField(models.CharField(max_length=8000, blank=True))**
eng_article = ArrayField(models.CharField(max_length=8000, blank=True))**
Thanks
You are running zip on the top level list, the Newspapers queryset in a list of lists, instead of the two lists of articles (eng_articles and esp_articles).
class ArticleViewPage(DetailView):
model = Newspapers
template_name = 'rtves/article.html'
context_object_name = 'news'
def get_context_data(self, **kwargs):
zip_scripts = zip(self.object.eng_articles, self.object.esp_articles)
context = super().get_context_data(**kwargs)
context.update(zip_scripts=zip_scripts)
return context
This is assuming ArticleViewPage should only show English and Spanish articles for a single newspaper at a time.
As an alternate solution you could instead of zipping the articles in the view's get_context_data method, make the zipped articles available on the model like so:
class Newspapers(models.Model):
esp_articles = ArrayField(models.CharField(max_length=8000, blank=True))
eng_articles = ArrayField(models.CharField(max_length=8000, blank=True))
#property
def articles_zipped(self):
return zip(self.esp_articles, self.eng_articles)
and update your template to use the new model property:
{% for article_spanish, article_english in news.articles_zipped %}
<p>{{ article_spanish }}</p>
<p>{{ article_english }}</p>
{% endfor %}
You need to add a simple code to fix your queryset. values_list returns a tuple. In order to make it return a list, you need to add flat=True.
Try this:
eng_articles = Newspapers.objects.values_list('eng_article', flat=True)
esp_article = Newspapers.objects.values_list('esp_article', flat=True)
In your querysets you should use values() to get a list of dicts with each dict corresponding to a row in the database. Then, in your template, you can use . to access a single column value (for each row).
views.py:
class ArticleViewPage(DetailView):
model = Newspapers
template_name = 'rtves/article.html'
context_object_name = 'news'
def get_context_data(self, **kwargs):
eng_articles = Newspapers.objects.values('eng_articles')
esp_articles = Newspapers.objects.values('esp_articles')
zip_scripts = zip(eng_articles, esp_articles)
context = super().get_context_data(**kwargs)
context.update(zip_scripts=zip_scripts)
return context
rtves/article.html:
{% for article_eng, article_esp in zip_scripts %}
<p>{{ article_eng.text }}</p>
<p>{{ article_esp.text }}</p>
{% endfor %}
I'm not sure what your models.py looks like. Are you fetching a list of related articles in English and Spanish for a single newspaper or all newspapers? Note: It is recommended to use singular nouns as model names, that is Newspaper without the s at the end.
I'm trying to understand the best way to display ForeignKey filtered data in a Django model.
I have three models reduced to this:
// models.py
class Publisher(models.Model)
def publisher_name = models.TextField()
def publisher_slug = models.SlugField()
def founded_year = models.IntegerField()
class Album(models.Model)
def album_name = models.TextField()
def publisher = models.ForeignKey('Publisher', related_name='albums')
class Song(models.Model)
def song_name = models.TextField()
def album = models.ForeignKey('Album', related_name='songs')
def published_year = models.IntegerField()
I have a URL that is composed of: /<publisher>/<published_year>/
The view I'm having trouble composing is supposed to be details like this:
Title of: Publisher.publisher_name
List of All Albums by the publisher: List of All songs from that album published the same year as the publisher__published_year: List of All songs from that album published as the url
The way, I've tried to do this that works right now is similar to this:
// views.py
class SongYearView(TemplateView):
def get_context_data(self, **kwargs):
context = super(SongYearView, self).get_context_data(**kwargs)
context['publisher'] = Publisher.objects.get(slug=kwargs['publisher_slug']
album_list=[]
for album in context['publisher'].albums.all():
single_album = dict()
single_album['album'] = album
single_album['publisher_year_song'] = album.songs.filter(published_year=context['publisher'].published_year)
single_album['filtered_year_song'] = album.songs.filter(published_year=kwargs['published_year']
album_list.append(single_album)
context['albums'] = album_list
return context
Then in the template I'm doing (with stripped out formatting)
// template.html
{{ publisher.name }}
{% for album in albums %}
{{ album.album.album_name }}
{% for song in album.publisher_year_song %}
{{ song.song_name }}
{% endfor %}
{% for song in album.filtered_year_song %}
{{ song.song_name }}
{% endfor %}
{% endfor %}
While this does work, it's not pretty and I'm pretty sure there are better ways of doing this.
This is an odd example, but just a basic example of my more detailed models. The way to think about it is Publisher -> Album -> Song or A -> B -> C. And I'm trying to get a view of all B items, that are only linked with a specific A item and then get two sets of C items for each B item, where one set is filtered on an A property and the other set is filtered on a passed argument from a URL.
I tried to get a custom model.Manager to help get this constructed, but didn't have much luck.
You could do add a custom template filter of_year:
#register.filter
def of_year(songs, year):
return songs.filter(published_year=year)
And change your template to
// template.html
{{ publisher.name }}
{% for album in publisher.albums %}
{{ album.album.album_name }}
{% for song in album.songs|of_year:publisher.founded_year %}
{{ song.song_name }}
{% endfor %}
{% for song in album.songs|of_year:filtered_year %}
{{ song.song_name }}
{% endfor %}
{% endfor %}
And clean your view:
// views.py
class SongYearView(TemplateView):
def get_context_data(self, **kwargs):
context = super(SongYearView, self).get_context_data(**kwargs)
context['publisher'] = Publisher.objects.get(slug=kwargs['publisher_slug'])
context['filtered_year'] = kwargs['published_year']
return context
Edit: rename the template filter
I'm currently working on a Django project and I have the current models
from django.db import models
class Topic(models.Model):
def __str__(self):
return self.topic_text
topic_text = models.CharField(max_length=100)
class Subtopic(models.Model):
def __str__(self):
return self.subtopic_text
topic = models.ForeignKey(Topic)
subtopic_text = models.CharField(max_length=100)
class Question(models.Model):
def __str__(self):
return self.question_text
topic = models.ForeignKey(Topic)
subtopic = models.ForeignKey(Subtopic)
question_text = models.CharField(max_length=200)
mod_date = models.DateTimeField('date modified')
pub_date = models.DateTimeField('date published')
answer_text = models.TextField()
After passing that through my views
def index(request):
question_list = Question.objects.all()
topic_list = Topic.objects.all()
subtopic_list = Subtopic.objects.all()
context = {
'topic_list': topic_list,
'subtopic_list': subtopic_list,
'question_list': question_list
}
return render(request, 'interview/index.html', context)
And the following template
<ul>
{% for topic in topic_list %}
<li>{{ topic.topic_text}}</li>
{% for subtopic in subtopic_list %}
{{topic.topic_text}} , {{subtopic.topic}}
**THIS HERE DOES NOT WORK**
{% if subtopic.topic == topic.topic_text %}
<ul>
<li>{{ subtopic.subtopic_text}}</li>
<ul>
{% endif %}
{% for question in question_list %}
<li>{{ question.question_text }}</li>
{% endfor %}
</ul>
{% endfor %}
</ul>
{% endfor %}
</ul>
However the line in the if statement is not working. When I change the == to != it prints (the wrong stuff) so I know it's not my syntax but just the fact they are not equal; however, when I check it with
{{topic.topic_text}} , {{subtopic.topic}}
Finance , Finance
Finance , Coding
Finance , Finance
Coding , Finance
Coding , Coding
Coding , Finance
My thought is that for DTL it's like Java and that you can't compare strings using the == ... but I couldn't find anything unique about string comparisons for DTL.
It's because you're comparing an object to a string.
You can also remove your second foreign key from Question since you already have one on Subtopic that points to Topic.
You would reference it as such:
{{Question.Subtopic.Topic.topic_text}}
which will give you the exact same thing.
After doing additional testing, I get the following:
{% if topic.topic_text == 'Finance' %}
Evaluates to true
{% if subtopic.topic == 'Finance' %}
Evaluates to false
Now, I'm not 100% sure of the cause but I'm thinking it has to do with the fact that subtopic.topic is actually pointing to the Topic Primary Key (Which is not set to topic_text) and then Django 'hides' it AFTER the comparison with the following function. Maybe? I'm going to make some changes to the model and let you guys know.
def __str__(self):
return self.topic_text
So apparently, Django does the comparison before the output so what was happening was it was comparing topic.topic_text (A string) to subtopic.topic (An object) and then transforming the object into string based on the def __ str __ function I typed above. I solved it by doing the following
{% if subtopic.topic.topic_text == topic.topic_text%}
You can use ifequal for this
{% if subtopic.topic == topic.topic_text %} can be changed to {% ifequal subtopic.topic topic.topic_text %}
Thank should fix your issue https://docs.djangoproject.com/en/1.8/ref/templates/builtins/
My view looks like this:
class PageView(DetailView):
queryset = Page.objects.all()
template_name = 'page.html'
def get_context_data(self, **kwargs):
context = super(PageView, self).get_context_data(**kwargs)
context['category'] = Category.objects.all()
context['categoryitem'] = CategoryItem.objects.all()
return context
When in template I try to execute the given context variables like so {{ category }} it prints out [<Category: Something not so interesting>] aka model name + it's title, I presume the title gets printed out because I've set __unicode__(self): return self.title in the model.py, but I cannot access any other fields from the given object. category.id is blank as so is everything else. How can I access those?
Your code is:
context['category'] = Category.objects.all()
So it should be:
context['categories'] = Category.objects.all()
And in your template:
{% for category in categories %}
{{ category.name }}
{% endfor %}
The output you got in your test makes sense:
[<Category: Something not so interesting>]
it's an array with only one entry, this entry is an object of the class Category, and the string representation of it is "Something not ..."
You need to iterate over the category, since it's queryset. E.g. in your template, you can do
<ul>
{% for c in category %}
<li> {{ c }} </li>
{% endfor %}
</ul>
category in template is queryset ( list of objects) not one single object. You need to iterate over it as
{%for c in category %}
{{c.id}} : {{ c.other_attribute }}
{%endfor%}