Django - Private messaging conversation view - python

I have built a very basic private messaging module for my Django project.
I have a Message model which consists of:
sender (Foreign key to the member model)
recipient (Foreign key to the member model)
message
date (Datetime of which the message was created)
Now my issue is i would like to create a new view which returns a list of conversations based on these messages.
I am trying to write a query which returns the latest messages but unique where sender = current user OR recipient = current user. This is so that i have a list of latest messages, which should be the equivalent of a conversation list. Have i got that bit right or am i completely over thinking things?
conversations = Message.objects.filter(Q(recipient=request.user) |
Q(sender=request.user)).annotate(max=Max('date'))
but this is returning duplicate conversations I tried this from another stack overflow post:
conversations = Message.objects.order_by('recipient', 'sender',
'date').distinct('recipient', 'sender')
but I'm receiving this error "DISTINCT ON fields is not supported by this database backend"
Any help would be greatly appreciated.

As #Grimmy stated in the comments, please post more information. Specifically, please add to your answer the query you tried (which you already have in the comments) and the result, and what is wrong with the result (you say "duplicate converstations; do you mean each conversation shows up just twice, or more than twice?)
Two options come to mind for this situation:
You could run a query on your existing model that removes/excludes duplicates.
You could create a new model called Conversation which holds information about the conversation, including members, date started, etc., and add a foreign key to each message assigning it one conversation (many-to-one from message to conversation). This would simplify your queries and would be easily extendable to accommodate "group chats" if need be (just add more members to the conversation), or things like a picture or title for the conversation itself.
When you post more information, I'll address option one in more detail if you would like. For now, I'll just say that if you are getting duplicate conversations where each conversation only shows up twice (once where the user is the sender and once where the user is the recipient), then it sounds like your query is good enough and you can just write a for loop in python that sorts through the resulting conversations and removes the older duplicates. As long as each user is in no more than over one or two hundred conversations, which sounds like the case, that shouldn't slow down performance too much.
But in any event, I recommend option two. I had to implement a similar feature in a Django project, and I went with option 2. It greatly simplifies your query to look like this:
# get the conversations the user is in
conversation_list = Message.objects.filter(conversation__participants=user).order_by('-date')
# get a list of the most recent message of each conversation
message_list = conversation_list.values('conversation').annotate(
first_msg=Max('conversation__message')
)
In order for that second line to sort messages properly, add the following to your Message model:
class Message(models.Model):
# sender field is unchanged
# date field is unchanged (maybe rename to avoid name collisions with date module)
# message field is unchanged (maybe rename to 'content' to avoid confusion)
# make recipient many-to-many because I'd recommend having both the sender and the recipient listed as recipients,
# but you don't have to do that
recipient = models.ManyToManyField(User)
# conversation foreign key
conversation = models.ForeignKey(Conversation, blank=False, null=False)
# this simplifies sorting so the "Max('conversation__message')" line
# sorts on date rather than primary key
ordering = ["-date"]
Here is the Conversation model:
class Conversation(models.Model):
participants = models.ManyToManyField(User)
# example functionality you may wish to add later
group_name = models.CharField(max_length=512, default="Group", blank=False, null=False)
profile_picture = models.FileField(upload_to='uploads/', default='uploads/GroupChatIcon.jpg')

Related

Implement a facebook type simpler 'like' feature in backend

I am working on a website made in Django and i want to implement a facebook type (or any social media type) like button. I have a user table, and a post table, and i want users to be able to like the posts. I am confused about some particular things :
How can I bind user info with likes so that one user can like only one time?
My current approach is:
I am thinking of creating a new field in post table, say post_likes, which have list of users who currently like the post. Then, before rendering the 'post' template, i have to loop over all the users (in field post_likes) to check if the request.user likes the post or not (in views.py), and send that data to template (to change the ui of like button to liked), but this approach seems very naive to me, also slow. What could be the better approaches?
We can do this by creating two models: Post (that will in reality contain extra data like the message, author, etc.), and Like that is acts as a many-to-many relation between a Post and a User:
class Post(models.Model):
total_likes = models.IntegerField(default=0)
likes = models.ManyToManyField(User, through='app.Like')
def like(self, user):
_, created = Like.objects.get_or_create(user=user, post=self)
if created:
self.total_likes += 1
self.save()
#classmethod
def update_likes(cls):
cls.objects.annotate(total=Count('likes')).update(total_likes=F('total'))
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
So the like(..) function is used to like a post (with as parameter the user). We can thus call somepost.like(someuser). It will fetch or create a new Like object that maps to the specific post and the specific user. In case the Like instance already existed, nothing happend, otherwise we increment the total_likes field that stores the total number of likes.
Perhaps you do not want to update this counter every time a user likes a post: after all, it creates some extra load on the server. In that case, the if created: part can be omitted, and we regularly should call Post.update_likes. This function will perform an aggregate per Post that counts the number of likes, and it will update the total_likes field.
Regardless whether you update total_likes constantly, it is better to every now and then update the total number of likes per post. Since other views, models, etc. can - given you the developers are not looking very strict to this - remove Like objects. Furthermore Users can be removed, etc. and we have not much control about that. Yes we can create signals that are triggered after Likes and Users are removed (and created), but still there can be problems with counting the number of likes like race conditions. Therefore I would advice to periodically update the number of likes.

appengine datastore model for twitter like showing posts from users followed

I am working on a web system where a feature is similar to Twitter's concept of following a list of users and seeing their posts as a list.
The simple model I came up with requires join operation which is not available in datastore.
class Post(Model):
author = reference to user id
content = text content
class Following(Model):
author = reference to user id
followed_by = reference to user id
The frequent operation is to display a list of posts (sorted in time) from users followed by the current user.
With the above model, it can only be done in two steps:
authors = Following.author when Following.followed_by == current_user
posts = Posts with Posts.author in authors
Is there any way to achieve this more efficiently?
You can use a structure property to store all Posts within the Author object.
There's an interesting discussion here that might be interesting for you to choose which approach is the best for your use case.
You could use a single query for displaying posts if you change the algorithm a bit. You could track what posts would need to be displayed for a particular user, with entities like these:
class DisplayPost(Model):
#parent entity = user for which the post should be displayed
#key ID matches the Post's key ID
posted = datetime # if you want timed ordering in display
expiry = datetime # optional for periodic cleanup jobs
Whenever an author creates a new post you'd just launch task(s) to create such entities for every follower of the author.
Whenever you need to display posts for a user you'd make a single ancestor keys_only query to get the list of DisplayPost keys:
keys = DisplayPost.query(ancestor=user_key, ...).fetch(keys_only=True)
From this you obtain a corresponding list of Post keys and get the post with a get_multi() op, something along these lines:
post_keys = [ndb.Key(Post, key.id()) for key in keys]
posts = ndb.get_multi(post_keys)
This allows you a much faster response time when displaying posts, with no join and no IN (also problematic) ops. Better scalability. The price to pay is always preparing the DisplayPost, even if some of them will never be used (if the respective users don't even log in, for example).

Django: simple model for storing messages/notifications to/from users

I am trying to create a simple Notification/Message model for my Django app. This will store notifications from the site to the user, and messages from one user to another. A working model I had been using looks like this:
class Notification(models.Model):
sender = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='sender_notification')
recipient = models.ForeignKey(User, on_delete=models.CASCADE, related_name='recipient_notification')
message = models.TextField()
read = models.BooleanField(default=False)
recieved_date = models.DateTimeField(auto_now_add=True)
A message will have 0-1 senders (0 if the message is a notification from the site to the user), and typically one recipient (when the notification is meant for one specific user - e.g., "there is a comment on your post" - or when a message has been sent from one user to another). This had been working for me. However, it occurred to me that in some cases I want to send a notification to every user. I could potentially create Notification objects in a loop:
for user in User.objects.all():
Notification.objects.create(recipient=user, message='message for all uses')
But this seems like it will be pretty inefficient, and create unnecessary clutter in the database.
I experimented with updating my model like this:
class Notification(models.Model):
sender = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='sender_notification')
recipient = models.ManyToManyField(User)
message = models.TextField()
read = models.BooleanField(default=False)
recieved_date = models.DateTimeField(auto_now_add=True)
But this requires a sender (for reasons that I don't understand), and since there is only one record for each notification, when one recipient reads the notification and I set read = True, it will show as read for all recipients.
Is there an easy solution that I am missing where I can use a single model to handle notifications/messages, or am I going to have to add some additional models (or duplicate notifications in the notifications table)?
Forgive my ignorance of database design. It is definitely a weakness of mine that I am working on.
You're worried it will create clutter in your DB, but the fact is if you want to store information about whether each user has read a given message, for each message you need to store one record per user. There's no way around it.
How you want to store these messages is up to you. I think your original model is fine. Admdebian's solution is also fine, but it requires an additional model. The only upside is that it uses less space (but not fewer records) in the DB for your mass notifications, but this doesn't matter unless your app has a ton of users.
Also, in your original model I would change the received_date field to sent_date. received_date is misleading, because I'm guessing you're creating the Notification record in the backend before you've received confirmation that it's been read, and you're setting that field to the current timestamp with auto_now_add.
So, change received_date to sent_date, and add a read_date column, which you can update when the user actually reads the message. Better yet, replace the read column with read_date, which is either None (message hasn't been read) or has some datetime in it (message was read at that time). You're now storing more information with the same number of fields.
My solution is to create a ReadFlag model.
class ReadFlag(models.Model):
user = models.ForeignKey(User)
message = models.ForeignKey(Message)
created = ...

Per user soft-delete model design in Django

I am designing an application where users send/receive records and I would like deletes to be separated for each user listed in the record (one user's delete will not hide the record from the other user).
My base model design looks like this:
class BasePrivateMessage(TimeStampedModel):
accepted = models.NullBooleanField(default=None, null=True, blank=True)
# fields in question
archived_by_recipient = models.BooleanField(default=False)
archived_by_sender = models.BooleanField(default=False)
read = models.BooleanField(default=False,
help_text='Recipient has viewed.')
recipient = models.ForeignKey('accounts.CommonUserProfile',
related_name='%(class)s_received')
sender = models.ForeignKey('accounts.CommonUserProfile',
related_name='%(class)s_sent')
message_body = models.TextField()
Would it be an improvement to replace the archived_by_xxxx fields with a ManyToManyField to accounts.CommonUserProfile that is responsible for storing a list of users who have hidden (soft-deleted) the record? It seems that would make client-side code simpler down the line. How is soft-delete typically implemented on a per-user basis?
I don't think there is a general industry-standard solution for this. Just do whatever you feel will get the job done for you specific application.
From what I understood, your message can only be viewed by a sender as well as a recipient, in which case I see no reason to add a M2M field. It will only slow down your application as it will use extra resources to do the extra lookups. However if you will need to extend you application where many users will be able to see a single message (e.g. group conversation), then it would make sense to add M2M field.
Bottom line is do whatever fits you application needs. Like always, its a compromise between abstraction (more flexibility) to performance.

GAE Python: How to acquire user name and additional information?

I have a question regarding the user api for GAE Python.
For a new application for an already existing user base I would like to get two informations for a user. First is the name and the second would be a flag (0,1 or 2 in example).
It's an internal application and only people from the same domain can login. How can I access their information beyond whats in the users reference? And how could I set that additional flag?
Thanks alot.
from the User class you may get only the information it provides, everything else you have to solicit directly from your users using some kind of "Accounts" page, where they can input their name and/or change the value of a flag.
This information can be saved in the database as a separate entity UserData, preferably alond with user email or user_id received from the User class. Something along the lines of:
class UserData(db.Model) :
user = db.UserProperty()
name = db.StringProperty()
flag = db.IntegerProperty()

Categories

Resources