Search code examples
pythondjangodjango-modelspki

How to have two separate query-sets under the same class based view EX: User Profile and Friends


On an earlier question I asked: How to have two separate querysets under the same class based view. The initial problem was a simple due to a syntax error but after fixing it i got :get() got an unexpected keyword argument 'pk' I believe it to be a problem on how i wrote my views.

EDIT: Clarifying the issue: is how to have two querysets one calling User's pk info and one making a list of users under the same class.

views.py

class ViewProfile(generic.ListView):
        model = Post
        template_name = 'accounts/profile.html'
        def view_profile(request, pk=None):
            if pk:
                user = User.objects.get(pk=pk)
            else:
                user = request.user
            kwargs = {'user': request.user}
            return render(request, 'accounts/profile.html', kwargs)
    def get(self, request):
        users =User.objects.all()
        object_list= Post.objects.filter(owner =self.request.user).order_by('-timestamp')
        args ={'object_list':object_list,'users':users}
        return render (request, self.template_name, args)

urls.py

# profilepage
        url(r'^profile/$', views.ViewProfile.as_view(), name='view_profile'),

    # profilepage
    url(r'^profile/(?P<pk>\d+)/$', views.ViewProfile.as_view(), name='view_profile_with_pk'),

profile

<div class="col-md-4">
    <h1>Friends</h1>
      {%for user in users %}
      <a href="{% url 'accounts:view_profile_with_pk' pk=user.pk  %}">
        <h3>{{user.username}}</h3>
      </a>

      {%endfor%}
</div>

Solution

  • As you see, your view_profile_with_pk url pattern takes a pk paramter:

    url(r'^profile/(?P<pk>\d+)/$', views.ViewProfile.as_view(), name='view_profile_with_pk')
    

    But then in your ViewProfile view you have configured your get() method like:

    def get(self, request):
        # ...
    

    You can clearly notice that get() takes exactly two parameters, which are self and request. There is no pk anywhere. So Django doesn't know about it.

    Yet.

    You can add *args and **kwargs arguments to the get() function, which will take care of your url parameters:

    def get(self, request, *args, **kwargs):
        # ...
    

    And then you can freely get your pk inside the function:

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')  # <<<
    
        users = User.objects.all()
        object_list = Post.objects.filter(owner=request.user).order_by('-timestamp')
        context = {'object_list': object_list, 'users': users}
    
        return render(request, self.template_name, context)
    

    But I don't understand why you have defined the view_profile function, while you can implement it's logic inside the get() function:

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        owner = User.objects.get(pk=pk) if pk else request.user  # <<<
    
        users = User.objects.all()
        object_list = Post.objects.filter(owner=owner).order_by('-timestamp')
        # ________________________________^
        context = {'object_list': object_list, 'users': users}
    
        return render(request, self.template_name, context)
    

    This will work, until you provide a non-existing pk to the view. This will result an exception. You can fix that by using get_object_or_404():

    from django.shortcuts import get_object_or_404
    
    
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        owner = get_object_or_404(User, pk=pk) if pk else request.user  # <<<
    
        users = User.objects.all()
        object_list = Post.objects.filter(owner=owner).order_by('-timestamp')
        context = {'object_list': object_list, 'users': users}
    
        return render(request, self.template_name, context)
    

    Now you should have a fully functional view.

    EDIT:

    As you've just stated, the view returns the same User data. This is because of:

    users = User.objects.all()
    

    This line is meaningless, because every time the user sends a request to your view, he will get a users property in the context, which just holds all the users in your database.

    A possible and more meaningful solution will be to attach the current user to this property:

    # ...
    context = {'object_list': object_list, 'user': owner}
    # _______________________________________^
    
    return render(request, self.template_name, context)
    

    Now the view will return the Posts and their related User.