Filter queryset based on another model's foreign key in a class based view.

1,254 views
Skip to first unread message

Ajat Prabha

unread,
Jun 10, 2017, 11:22:20 AM6/10/17
to Django users
Hi there,
I'm having trouble in filtering objects of a model based on another model's foreign key. The scenario is, I've got a forum homepage and when a topic is clicked, it opens its DetailView. Now if I try to get all answers in the database there's no issue, but I want to get only the answers which are there only for that particular topic.

here are my project files

views.py

from django.views import generic
from django.views.generic.edit import CreateView
from .models import Topic, Answer
from .forms import TopicForm
from django.contrib.auth.decorators import login_required

class IndexView(generic.ListView):
    template_name = 'index.html'
    context_object_name = 'topic_list'
    def get_queryset(self):
        return Topic.objects.all()

 
class TopicDetailView(generic.DetailView):
    model = Topic
    context_object_name = 'topic'
    template_name = 'topicdetail.html'
 
    def get_context_data(self, **kwargs):
        context = super(TopicDetailView, self).get_context_data(**kwargs)
        context["answer"] = Answer.objects.filter(<can't figure out this>)
        return context 
 
 
class TopicCreateView(CreateView):
    model = Topic
    form_class = TopicForm
    template_name = 'topic_form.html'

models.py

from django.db import models
from oauth.models import UserProfile
from ckeditor_uploader.fields import RichTextUploadingField
from django.core.urlresolvers import reverse

 
class Topic(models.Model):
    # Choices
    CAT_CHOICES = (
        ('Q', 'Question'),
        ('F', 'Feedback'),
    )
    # Topic Database Model
    owner = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
    category = models.CharField(max_length=3, choices=CAT_CHOICES, default='Q')
    title = models.CharField(max_length=256)
    content = RichTextUploadingField(blank=True)
    slug = models.SlugField(unique=True)
    views = models.PositiveIntegerField(default=0)
    answers = models.PositiveIntegerField(default=0)
    tags = models.CharField(max_length=50)
    created_at = models.DateTimeField(auto_now_add=True)
 
    def get_absolute_url(self):
        return reverse('forum:detail', kwargs={'pk': self.pk})
 
    def __str__(self):
        return self.title

class Answer(models.Model):
    topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
    owner = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
    content = RichTextUploadingField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    total_votes = models.SmallIntegerField(default=0)

urls.py

from django.conf.urls import url
from forum import views
from django.contrib.auth.decorators import login_required
 
app_name = 'forum'
 
urlpatterns = [
    url(r'^$', login_required(views.IndexView.as_view()), name='index'),
    url(r'^topic/(?P<pk>[0-9])/$', login_required(views.TopicDetailView.as_view()), name='detail'),
    url(r'^topic/add/$', login_required(views.TopicCreateView.as_view()), name='add_topic'),
]

What do I need to do to accomplish the same? 

Alceu Rodrigues de Freitas Junior

unread,
Jun 10, 2017, 11:39:21 AM6/10/17
to django...@googlegroups.com

See "reverse lookup" or "_set" function in order to do that.

Your Answer model has a FK for the Topic, but not the other way around.

See https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Generic_views, "Creating the Detail View template" for details about it, there are some limitations although.

In your case, your template would need to has:

{% for answer in topic.answer_set.all %}

To fetch all the answers.

Think about it now, I'm not sure if this violates somehow the concept of MVT: I understand that the View should be responsible to fetch all data from the Model and pass it to the template, but in this case the Template is calling methods from the object.

Regards,

--

Alceu

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/dc049d19-2379-439b-8b13-50292a697b04%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.







Melvyn Sopacua

unread,
Jun 10, 2017, 12:24:57 PM6/10/17
to django...@googlegroups.com

On Saturday 10 June 2017 08:22:20 Ajat Prabha wrote:

 

 

> class TopicDetailView(generic.DetailView):

> > model = Topic

> > context_object_name = 'topic'

> > template_name = 'topicdetail.html'

>

> def get_context_data(self, **kwargs):

> > context = super(TopicDetailView,

> > self).get_context_data(**kwargs)

> > context["answer"] = Answer.objects.filter(<can't figure out

> > this>)

 

Use clear and consistent names. Since this can return multiple answers, use answer_list, as in line with topic_list in your index view.

this becomes:

context['answer_list'] = self.object.answer_set.all()

 

 

 

> models.py

 

> class Topic(models.Model):

> > # Choices

> > CAT_CHOICES = (

> >

> > ('Q', 'Question'),

> > ('F', 'Feedback'),

> >

> > )

> > # Topic Database Model

> > owner = models.ForeignKey(UserProfile, on_delete=models.CASCADE)

> > category = models.CharField(max_length=3, choices=CAT_CHOICES,

> >

> > default='Q')

> >

> > title = models.CharField(max_length=256)

> > content = RichTextUploadingField(blank=True)

> > slug = models.SlugField(unique=True)

> > views = models.PositiveIntegerField(default=0)

> > answers = models.PositiveIntegerField(default=0)

 

Why is this here?

 

Is this what you want?

 

@property

def number_of_answers(self):

return self.answer_set.count()

 

 

> > tags = models.CharField(max_length=50)

> > created_at = models.DateTimeField(auto_now_add=True)

>

> def get_absolute_url(self):

> > return reverse('forum:detail', kwargs={'pk': self.pk})

>

> def __str__(self):

> > return self.title

> >

> > class Answer(models.Model):

> > topic = models.ForeignKey(Topic, on_delete=models.CASCADE)

 

This creates a property 'answer_set' on the Topic model, which is a model manager (to be precise, a RelatedManager). So you can do queryset methods on it.

 

 

> urls.py

 

> > url(r'^$', login_required(views.IndexView.as_view()),

 

Consider moving this to the view definitions:

 

class IndexView(LoginRequiredMixin, ListView):

 

(LoginRequiredMixin *must* be first).

 

 

--

Melvyn Sopacua

Ajat Prabha

unread,
Jun 10, 2017, 2:19:28 PM6/10/17
to Django users
Thanks, Melvyn Sopacua,
Now it all makes sense.
Reply all
Reply to author
Forward
0 new messages