Creating a DetailView of a profile with a queryset of posts created by that user

Question:

I’m creating a twitter-like app and I’m stuck on creating a UserProfileView which is supposed to display a certain User’s profile, along with a list of posts made by that user below.Though I can’t really figure out a way to create a proper view for that.
I’m trying to use class based views for that, the one I’ll be inheriting from is probably DetailView (for profile model) and something inside of that which retrieves a queryset of posts made by that user –

My profile model looks like this:

class Profile(models.Model):
    user = models.OneToOneField(
        User, on_delete=models.CASCADE, primary_key=True)
    display_name = models.CharField(max_length=32)
    profile_picture = models.ImageField(
        default='assets/default.jpg', upload_to='profile_pictures')
    slug = models.SlugField(max_length=150, default=user)

    def get_absolute_url(self):
        return reverse("profile", kwargs={"pk": self.pk})

Post model:

class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    date_posted = models.DateField(auto_now_add=True)
    content = models.TextField(max_length=280)
    image = models.FileField(upload_to='post_images/', blank=True, null=True)

    def __str__(self) -> str:
        return f'Post by {self.author} on {self.date_posted} - {self.content[0:21]}'
    
    def get_absolute_url(self):
        return reverse("post-detail", kwargs={"pk": self.pk}) 

I’ve tried creating this method:

class UserProfileView(DetailView):

    model = Profile
    context_object_name = 'profile'
    template_name = 'users/profile.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['user_posts'] = Post.objects.filter(author=Profile.user)
        return context

But this one sadly doesn’t work, raising an error of

"TypeError: Field 'id' expected a number but got <django.db.models.fields.related_descriptors.ForwardOneToOneDescriptor object at 0x000001A5ACE80250>."

‘ForwardOneToOneDescriptor’ object has no attribute ‘id’ is returned if I replace the filter argument with author=Profile.user.id

I’m not sure whether it’s a problem with the way I filtered Posts, or how I used get_context_data.
I’ve been stuck on this for a lot of time now and I feel very frustrated, please help me out.

Asked By: wisie

||

Answers:

The object is stored as self.object, so you can filter with:

class UserProfileView(DetailView):
    model = Profile
    context_object_name = 'profile'
    template_name = 'users/profile.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['user_posts'] = Post.objects.filter(author_id=self.object.user_id)
        return context

An alternative might be to use a ListView for the Posts instead, to make use of Django’s pagination:

from django.shortcuts import get_object_or_404
from django.views.generic import ListView


class UserProfileView(ListView):
    model = Post
    context_object_name = 'posts'
    template_name = 'users/profile.html'
    paginate_by = 10

    def get_queryset(self, *args, **kwargs):
        return (
            super()
            .get_queryset(*args, **kwargs)
            .filter(author__profile__slug=self.kwargs['slug'])
        )

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['profile'] = get_object_or_404(Profile, slug=self.kwargs['slug'])
        return context

Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Answered By: Willem Van Onsem