Show list of related objects in Django
I have an issue with displaying list of related articles in my Q&A DetailView. I have a field where user can connect an article to Q&A from admin site. What I want is to display these related article
models.py
class QA(models.Model):
id = models.AutoField(primary_key=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL) #settings INSTALLED_APPS
title = models.CharField(max_length=750)
category = models.ForeignKey(Category, on_delete = models.CASCADE, blank=True)
related_articles = models.ManyToManyField(Article, default=None, blank=True, related_name='related_article')
slug = models.SlugField(unique=True, blank=True)
class Article(models.Model):
id = models.AutoField(primary_key=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL) #settings INSTALLED_APPS
title = models.CharField(max_length=200)
category = models.ForeignKey(Category, on_delete = models.CASCADE, blank=True)
slug = models.SlugField(unique=True, blank=True)
views.py
class QADetailView(LoginRequiredMixin, DetailView):
login_url = 'login'
redirect_field_name = 'login'
template_name = 'QADetailView.html'
model = QA
def get_context_data(self, **kwargs):
categories = Category.objects.all()
related_articles = Article.objects.filter(related_article=self.kwargs['id']) #No idea what to put in filter
#related_articles = Article.objects.filter(related_article='1')
context['related_article'] = related_articles
context['categories'] = categories
return context
QADetailView.html
{% for article in related_article %}
{{article.title}}
{% endfor %}
You don' t need to inject the related articles in the template context, you can simply write in your QADetailView.html
template (without any necessary edit):
{% for article in object.related_articles.all %}
{{article.title}}
{% endfor %}
Check RedWheelBorrow's solution first. If that does not work. Try the following:
There might be a better way of structuring your classes. So in Django, it is possible to simulate a hierarchy. For instance, when creating an invoice representation it would look something like this.
from django.db import models
class Invoice(models.Model):
"""Representing a invoice"""
user = models.ForeignKey(to=User, on_delete=models.PROTECT, related_name="invoices", default=1)
title = models.CharField(max_length=200)
date = models.DateField()
start_time = models.TimeField(default=time(9))
duration = models.IntegerField(default=1)
invoice_number = models.CharField(max_length=500, default=increment_invoice_number) # increment_invoice_number this a function that I will leave out of this answer
class InvoiceLine(models.Model):
"""Representing invoice lines of an invoice"""
invoice = models.ForeignKey(to=Invoice, on_delete=models.CASCADE, related_name="lines")
description = models.CharField(_("Beschrijving"), max_length=512)
quantity = models.IntegerField(_("Aantal"), blank=True, default=1)
discount = models.IntegerField(_("Korting in %"), default=0)
Please note, the invoice representation as presented here lacks some attributes to be a fully functional class in production. It still needs tax references, etc. However, it holds the solution to your problem.
The Invoice class has an attribute user that holds a ForeignKey with 'invoices' being its related name. This means it the object must be linked to a User object. A User can have multiple Invoices. An Invoice can only be linked to one User. It is one-to-many relationship:
User -> List[Invoice,...]
When looking at the class InvoiceLine we see a similar pattern. The attribute invoice is a ForeignKey with a link to the Invoice class and holds 'lines' as related name. This is also an one-to-many relationship.
Invoice -> List[InvoiceLine, ...]
To obtain the linked objects we can use the following code:
# obtain user
user = User.objects.get(id=<USER_ID>)
# obtain all Invoice objects linked to user
invoices = user.invoices.all()
# print all string representations of Invoice objects
print(invoices)
# obtain all InvoiceLine objects linked to invoices
for invoice in invoices:
lines = invoice.lines.all()
print(lines)
In the above example the highest object is User. A User can hold multiple Invoice objects. A Invoice object can hold multiple InvoiceLine objects. We can use the same strategy to solve your problem.
We want the following representation:
User -> List[QA, ...] QA -> List[Article, ...]
class QA(models.Model):
id = models.AutoField(primary_key=True)
# So in your case the author is the user.
# Here you define User -> List[QA, ...]
author = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL) #settings INSTALLED_APPS
title = models.CharField(max_length=750)
category = models.ForeignKey(Category, on_delete = models.CASCADE, blank=True)
slug = models.SlugField(unique=True, blank=True)
# related_articles is removed.
class Article(models.Model):
id = models.AutoField(primary_key=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL) #settings INSTALLED_APPS
title = models.CharField(max_length=200)
category = models.ForeignKey(Category, on_delete = models.CASCADE, blank=True)
slug = models.SlugField(unique=True, blank=True)**
related_articles = models.ForeignKey(to=QA, on_delete=models.CASCADE, related_name="related_articles")
class QADetailView(LoginRequiredMixin, DetailView):
login_url = 'login'
redirect_field_name = 'login'
template_name = 'QADetailView.html'
model = QA
def get_context_data(self, **kwargs):
categories = Category.objects.all()
# obtain specific QA
qa = QA.objects.get(pk=id, author=self.request.user)) # check how you named the id variable in your url.
# obtain related articles
related_articles = qa.related_articles.all()
# Save in context for use in template.
context['related_articles'] = related_articles # Added an character 's' to key value for there is can be more than one related article.
context['categories'] = categories
return context
{% for article in related_articles %} # Also add character 's' here
{{article.title}}
{% endfor %}
This should do the trick. There might be some improvements on error handling though I hope this works. If not, let me know.