Search code examples
djangolistviewdjango-querysetvalueerrorinstant-messaging

Django ValueError: Cannot query "user": Must be "Profile" instance


I am trying to add simple user to user messaging functionality to my blog app in Django. I have a view that allows a user to send a message but I am having difficulty in displaying the messages after. Here is my Message model:

class Message(models.Model):
    user = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='messages')
    sender = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='messages_from')
    message = models.TextField(blank=True)
    timestamp = models.DateTimeField(default=timezone.now, editable=False)
    unread = models.BooleanField(default=True) 

And the related Profile model:

class Profile(models.Model):
    first_name = models.CharField(max_length=100, blank=True)
    last_name = models.CharField(max_length=100, blank=True)
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    slug = models.SlugField(unique=True, blank=True)
    updated = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True)

Here is my MessagesView attempt:

class MessagesView(ListView):
    model = Profile
    template_name = 'users/messages.html'
    context_object_name = 'msg'

    def get_queryset(self):
        return Profile.objects.filter(messages_from__isnull=False, messages__user_id=self.request.user)

And here is the url:

path('messages/<int:pk>/', MessagesView.as_view(), name='messages'),

Upon trying to load this page I receive the error Cannot query "user": Must be "Profile" instance where "user" is the name of the logged in user who's messages I am attempting to load. I have spent a long time googling a solution to this but have found nothing that relates to my case. Please help


Solution

  • Your Message has as user field a ForeignKey to Profile, not User (therefore it might be better to rename the field to profile). This thus means that filtering like messages__user_id=self.request.user does not make much sense.

    You can filter by following the relation from Profile to User with:

    class MessagesView(ListView):
        model = Profile
        template_name = 'users/messages.html'
        context_object_name = 'msg'
    
        def get_queryset(self):
            return Profile.objects.filter(
                messages_from__isnull=False,
                messages__user__user=self.request.user
        ).distinct()

    The .distinct() [Django-doc] is necessary here to prevent retrieving the same Profile multiple times.

    Since this is a MessagesView, you furthermore probably should return Messages, the fact that you set the context_object_name to msg also hints to that. In that case you thus should return a QuerySet of Messages, not Profiles:

    class MessagesView(ListView):
        model = Message
        template_name = 'users/messages.html'
        context_object_name = 'msg'
    
        def get_queryset(self):
            return Message.objects.filter(
                user__user=self.request.user
            )

    In case you rename the ForeignKey to profile, you filter with profile__user=self.request.user.


    Note: You can limit views to a class-based view to authenticated users with the LoginRequiredMixin mixin [Django-doc].


    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.