I have two basic models, Story and Category:
class Category(models.Model):
title = models.CharField(max_length=50)
slug = models.SlugField()
...
class Story(models.Model):
headline = models.CharField(max_length=50)
slug = models.SlugField()
categories = models.ManyToManyField(Category)
...
And my view for story detail:
from django.views.generic import date_based, list_detail
from solo.apps.news.models import Story
def story_detail(request, slug, year, month, day):
"""
Displays story detail. If user is superuser, view will display
unpublished story detail for previewing purposes.
"""
stories = None
if request.user.is_superuser:
stories = Story.objects.all()
else:
stories = Story.objects.live()
return date_based.object_detail(
request,
year=year,
month=month,
day=day,
date_field='pub_date',
slug=slug,
queryset=stories,
template_object_name = 'story',
)
On the view for a given story object -- I'm using a generic detail view -- I'd like to display a list of stories related to the current story via the categories applied to the current story.
Here's how I'm doing this currently in the story detail template:
{% for category in story.category.all %}
<ul id="related_stories">
{% for story in category.story_set.all|slice:"5" %}
<li>{{ story.headline }}</li>
{% endfor %}
</ul>
{% endfor %}
This provides me what I need except I'd like to avoid displaying the linked headline for the story I'm viewing currently.
I believe this is done via the "exclude" filter, but I'm not sure if this belongs on the Category or Story model as a method, or how to construct it.
Any help would be appreciated!
Do this:
class Story(models.Model):
...
#property
def related_story_set(self):
category_id_list = self.category.values_list("id", flat=True)
return Story.objects.filter(category__id__in=category_id_list).exclude(id=self.id)
Then you can do this in the template:
<ul id="related_stories">
{% for related_story in story.related_story_set.all|slice:"5" %}
<li>{{ related_story.headline }}</li>
{% endfor %}
</ul>
You could just check in the template if the currently iterated story is the original story:
{% for category in story.category.all %}
<ul id="related_stories">
{% for substory in category.story_set.all|slice:"5" %}
{% if substory != story %}
<li>{{ story.headline }}</li>
{% endif %}
{% endfor %}
</ul>
{% endfor %}
You asked to put it in a model method:
class Story(models.Model):
...
def get_categories_with_stories(self):
categories = self.category.all()
for category in categories:
category.stories = category.story_set.exclude(id=self.id)[:5]
return categories
This doesn't solve your expensive query issue, but that wasn't a part of the question.
{% for category in story.get_categories_with_stories %}
<ul id="related_stories">
{% for substory in category.stories %}
<li>{{ story.headline }}</li>
{% endfor %}
</ul>
{% endfor %}
Related
I have a web page with essays. Each essay (on its own sub-page) can have multiple videos from another model (linked with many-to-many relations).
When I had - in the past - one video in a subpage with a particular essay (with a different column related to the video, in the same model: 'one-to-many model', now hashed in my models.py) then I used to be able to invoke the video in my essay page with:
<p>{% video essay_videos.video.video_item_url %}</p>
EDIT: *The code above just prove that library embed-video works, so all settings are ok. Now, this code is no longer in use - hashed in models.py and in my template. All code below is related to my problem and IS NOT related to <p>{% video essay_videos.video.video_item_url %}</p>.
Right now "behind" essay_videos there is a different column in models.py with many-to-many relation that connects the "video" model with the "essay" model.*
But now when I try to invoke a list of videos, with this new code:
<ul>
{% for vid in essay_videos %}
<li>{{ vid.video_item_url }}</li>
{% endfor %}
</ul>
I'm getting a list of URLs - that's ok, but, when I try to use the embed-video tag (like before):
<ul>
{% for vid in essay_videos %}
<li>{% video %}{{ vid.video_item_url }}{% endvideo %}</li>
{% endfor %}
</ul>
or
<ul>
{% for vid in essay_videos %}
{% video %}
<li>{{ vid.video_item_url }}</li>
{% endvideo %}
{% endfor %}
</ul>
or
<ul>
{% video %}
{% for vid in essay_videos %}
<li>{{ vid.video_item_url }}</li>
{% endfor %}
{% endvideo %}
</ul>
...In all cases, I've got Exception Type: IndexErrorm, Exception Value: pop from empty list
My models.py:
...
from embed_video.fields import EmbedVideoField
...
class VideoObject(models.Model):
video_item_url = EmbedVideoField() # same like models.URLField()
added_date = models.DateTimeField(auto_now_add=True, null=True)
title_video = models.CharField(max_length=200)
class Meta:
ordering = ['-added_date']
class EssayCls(models.Model):
title = models.CharField(max_length=200)
organizer_email = models.EmailField(null=True)
date = models.DateField(null=True)
slug = models.SlugField(unique=True, db_index=True)
description = models.TextField(validators = [MinLengthValidator(10)])
details = models.TextField()
image = models.ImageField(upload_to='images')
# video = models.ForeignKey(VideoObject, blank=True, null=True, on_delete=models.SET_NULL, related_name="video_obj") # One-to-many relationship
video = models.ManyToManyField(VideoObject, blank=True, related_name="video_obj") # Many-to-many relationship
language = models.ForeignKey(ProgLang, null=True, on_delete=models.SET_NULL)
guest = models.ManyToManyField(SendMeMessage, blank=True)
tags = models.ManyToManyField(Tag)
My views.py:
class MyEssaysView(View):
def get(self, request, slug):
user_agent = get_user_agent(request)
selected_essay = EssayCls.objects.get(slug=slug)
user_feedback = UserFeedback() # handling form submission 3.18.20
context = {
'essay_found': True,
'essay_all': selected_essay,
'post_tags': selected_essay.tags.all(),
'comments': selected_essay.comments.all().order_by("-id"),
'essay_videos': selected_essay.video.all()
}
if request.method == 'GET':
if user_agent.is_pc:
return render(
request,
'dev/article-content_pc_tablet.html',
context
)
How to solve it? The problem is with DTL syntax, or somewhere else (views/models)?
EDIT:
{% video %} tag is related to embed-video library.
The traceback of the exception:
The problem was with the syntax in for loop.
A proper way of defining a list of videos from the context provided by a class-based view that uses two models that have a relation many-to-many with the embed-video library is:
<ul>
{% for vid in essay_videos %}
<li>{% video vid.video_item_url %} </li>
{% endfor %}
</ul>
The rest of the code was fine.
Hej! :)
I have a model to create single institutions (Institution) and can connect it to a parent institution, via parent_institution (self).
So I have the institution A, which is parent to b, c, d (All themselves single institutions with an own detail view.) In the detail view of b I have the section 'parent institution' where I get A as a result, including a link to the detail view of A.
<p><b>Parent institution: </b>
{% if institution.parent_institution %}
<a href="{% url 'stakeholders:institution_detail' institution.parent_institution.id %}">
{{institution.parent_institution}}
</a>
{% endif %}
</p>
Following this link I get to the detail view of A where I want a section with the child institutions. There should b, c, d be listed.
I added a related name to the parent_institution
class Institution(models.Model):
name = models.CharField(
verbose_name=_("Name of the institution"),
max_length=200,
)
parent_institution = models.ForeignKey(
"self",
verbose_name=_("Parent institution"),
on_delete=models.SET_NULL,
blank=True,
null=True,
help_text=_("if applicable"),
related_name="child",
)
normally I can follow the ForeignKey in the opposite direction via this related_name.
<p><b>Child institution: </b>
{{institution.child.name}}
</p>
but in this case this is not working and gives me 'None'. Therefor did I try:
{% if institution.id == institution.all.parent_institution.id %}
{{institution.name}}
{% endif %}
{% if institution.all.id == institution.parent_institution.id %}
{{institution.name}}
{% endif %}
{% for child in institutions.all %}
{% if child.id == institution.parent_institution.id %}
{{institution.name}}
{% endif %}
{% endfor %}
# views.py
class InstitutionDetail(DetailView):
model = Institution
def get(self, request, *args, **kwargs):
institutions_child = Institution.objects.filter(parent_institution__isnull=True).prefetch_related('parent_institution_set')
institutions = get_object_or_404(Institution, pk=kwargs['pk'])
context = {'institutions_child': institutions_child, 'institutions': institutions}
return render(request, 'stakeholders/institution_detail.html', context)
{% for child_node in institutions.parent_institution_set.all %}
{{child_node.name}}
{% endfor %}
I either get None or the current institutions name (A).
Does anyone know how I could achieve the goal of getting all the child institutions in the detail view?
Any help is appreciated! :)
The Foreign Key in opposite direction returns a queryset, not a model instance.
<p><b>Child institution: </b></p>
<ul>
{% for child in institutions.child.all %}
<li>{{ child.name }}</li>
{% endfor %}
</ul>
How can I access fields other than the grouper in a Django group_by function?
class dateEvent(models.Model):
event = models.ForeignKey('Event', on_delete=models.CASCADE)
start_date_time = models.DateTimeField(auto_now=False, auto_now_add=False)
def __str__(self):
return "%s" % (self.event.title)
def description(self):
return "%s" % (self.event.description)
class Event(models.Model):
description = RichTextUploadingField(max_length=200)
view:
def my_view(request):
events = dateEvent.objects.all()
context = {
'events': events,
}
return render(request, 'view.html', context)
template:
<ul>
{% for event in dateEvents_list %}
<li><h5>Event: {{ event.grouper }}</h5>
<h6>Description: {{ event.description }}</h6> #How can access the description of the main event?
<ul>
{% for dateEvent in event.list %}
<li>date: {{ dateEvent.start_date_time }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
I'd like to have the title, which is the grouper so it's fine, but also the description.
You can also access your grouper's list objects to get details about the "first record" in your group. So using:
event.grouper.list.0.<any_model_field_you_want>
Will work just fine. I have this in production and it solved my needs immediatly. I found this in a rejected feature request for Django here: https://code.djangoproject.com/ticket/13452
If you need to traverse your model's relationship tree you can do the following.
event.grouper.list.0.<related_model_name>.<any_model_field_you_want>
You can access model attributes from the grouper directly using event.grouper.<any-model-attribute>.
In your example here is how it would look:
<ul>
{% for event in dateEvents_list %}
<li><h5>Event: {{ event.grouper }}</h5>
<h6>Description: {{ event.grouper.description }}</h6>
<ul>
{% for dateEvent in event.list %}
<li>date: {{ dateEvent.start_date_time }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
I can't find any documentation around this usage.
I am having issues with accessing data through a django one to many relationship. After 3 painstaking days I figured out a way to display the data from the relationship by overriding the get_context_data method. I was wondering if that was the proper way to do it. This works but I could imagine that there is a better way to do this that I missed in the documentation.
Here is the code for that:
class QuestionDetailView(DetailView):
model = Question
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['answers'] = Answer.objects.filter(firm=context['object'])
return context
Here is the code for the models:
class Question(models.Model):
text = models.CharField(max_length=120, unique=True)
class Answer(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
Here is the code in my template:
{% extends "base.html" %}
{% block body %}
<div class="">
<h3>{{ object.text }}</h3>
<p>Answers:</p>
<ul>
{% for answer in answers %}
<li> {{ answer }}</li>
{%empty%}
<li>No answers</li>
{% endfor %}
</ul>
</div>
{% endblock %}
Add a related_name to your question field.
class Answer(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="answers")
Or, just use the default that Django gives: answer_set
Then in your template, you can do:
{% for answer in object.answers.all %}
<li> {{ answer }}</li>
{% empty %}
<li>No answers</li>
{% endfor %}
No need to override get_context_data unless you want to do something more specific with the queryset.
I have two models with one having a foreign key to the other as such:
Models:
class WhoAmI(models.Model):
name = models.CharField(max_length=200)
company = models.CharField(max_length=200)
def __str__(self):
return self.name
class SolarClient(models.Model):
name = models.CharField(max_length=200)
client_owner = models.ForeignKey(WhoAmI, on_delete=models.CASCADE, related_name='solarclients')
addr = models.CharField(max_length=200)
city = models.CharField(max_length=200)
state = models.CharField(max_length=200)
email = models.EmailField()
I am trying to simply display an html table showing each client a salesperson has, with the salesperson listed first with a table of clients below their name.
The only way I could figure out how to do this was to create a dictionary using the code shown below.
class Homeowners(DetailView):
def get(self, request, **kwargs):
salespersons = WhoAmI.objects.all()
homeowners = SolarClient.objects.all().order_by("client_owner") #the name 'objects' is the Manager
rangers = {}
for e in salespersons:
k = salespersons.get(id = e.id)
v = k.solarclients.all()
rangers[k] = v
return render(request, 'homeowners.html', {'homeowners': homeowners, 'salespersons': salespersons, 'rangers': rangers })
I then iterate over the dictionary using:
{% for key, values in rangers.items %}
... display salesperson
{% if values %}
{% for v in values %}
.... display clients
{% endfor %}
{% else %}
... display "No Clients"
{% endif %}
{% endfor %}
Is there a more efficient way to do this? It seems silly to put the data into a dictionary to display it, but after many, many hours of trying different methods, this is the only way I could display the data.
thanks for any suggestions.
views.py
class Homeowners(DetailView):
def get(self, request, **kwargs):
salespersons = WhoAmI.objects.all()
return render(request, 'homeowners.html', {'salespersons': salespersons })
html:
{% for sales in salespersons %}
{% for client in sales.solarclients.all %}
------ Print Client
{% empty %}
---- Client not exist
{% endfor %}
{% endfor %}
There is a nice handy template filter called regroup built in Django which does exactly what you're looking for.
# views.py
homeowners = SolarClient.objects.all().order_by("client_owner").select_related('client_owner')
return render(request, 'homeowners.html', {'homeowners': homeowners})
# homeowners.html
{% regroup homeowners by client_owner as home_owners_list %}
<ul>
{% for client_owner in home_owners_list %}
<b>{{ client_owner.grouper }}</b>
<ul>
{% for client in client_owner.list %}
<li>{{ client.name }}</li>
{% endfor %}
</ul>
{% endfor %}
</ul>
The select_related method is just for performance improvement and omitting it wouldn't affect functionality.