Search code examples
pythondjangomodelmany-to-many

Django conversation categories for messages with ManyToMany


I am making a kind of social network in Django. This explains basically what I'm doing:

Django - Private messaging conversation view

The conversations are exclusively between two people, not more. I have a conversation model because when they to their messages, I want the user to have a list of the other users that they have messages with.

Here is the relevant view:

def writemessage(request, id):
    profile = Profile.objects.get(id=id)
    context = {
        'profile': profile,
    }
    if request.method == 'POST':
        sender = request.user
        receiver = profile.user
        content = request.POST['content']
        timestamp = datetime.now()
        print(sender, receiver, content, timestamp)
        record = Message(sender=sender, receiver=receiver, content=content, timestamp=timestamp)
        record.save()
        senderprofile = Profile.objects.get(user=sender)
        receiverprofile = Profile.objects.get(user=receiver)
        record.conversation.add(senderprofile)
        record.conversation.add(receiverprofile)
        print(senderprofile, receiverprofile)
        return redirect('messagespage')
    return render(request, 'thecode/writemessage.html', context)

Here are my models:

from django.db import models
from django.contrib.auth.models import User

# Create your models here.

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    city = models.CharField(max_length=60)
    country = models.CharField(max_length=60)
    skillstolearn = models.CharField(max_length=200)
    skillstoteach = models.CharField(max_length=200)
    description = models.TextField()

    def __str__(self):
        return self.user.username

class Conversation(models.Model):
    participants = models.ManyToManyField(User)
    
    def __str__(self):
        return self.participants

class Message(models.Model):
    sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sender')
    receiver = models.ForeignKey(User, on_delete=models.CASCADE, related_name='receiver')
    content = models.TextField()
    conversation = models.ForeignKey(Conversation, default=None, on_delete=models.CASCADE, null=True)
    timestamp = models.DateTimeField()

    def __str__(self):
        return self.sender.username + ' to ' + self.receiver.username

I know about some documentation:

https://docs.djangoproject.com/en/dev/ref/models/relations/

I've tried to understand the documentation and also posts on here but am a little bit stuck.

'NoneType' object has no attribute 'add' = the error messages that I get


Solution

  • The Message object's conversation field is None by default, and your view function doesn't set conversation before trying to read it. You need to figure out what Conversation this message belongs in, and then set it when you construct the Message:

    record = Message(conversation=???, sender=sender, receiver=receiver, content=content, timestamp=timestamp)
            record.save()
    

    How do you know which conversation a User is in?

    A few code review notes:

    • The related_name arguments for your ForeignKey fields indicate the name of the field on the User model that refers back to the Message model, not the other way around. Therefore, instead of sender and receiver, they should be messages_sent and messages_received. Please update your original posting to improve the clarity.
      # Example of accessing sent and received messages from a User model:
      user.messages_sent
      user.messages_received