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.

Back to Top