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

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 = ...

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.

Django - Private messaging conversation view

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')

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.

Should I change my UserProfile table or create other tables to hold additiaonl user info after user login

In my project, after login, the user have to go through a few process to input their personal particulars, upload some files
Now I have finished the auth process
there are only three fileds in my UserProfile
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User)
#activation_key is used in my account registration process
activation_key = models.CharField(max_length=40)
#activation link should be only valid for one day
key_expires = models.DateTimeField()
def __unicode__(self):
return self.user.username
these three fileds are enough to make the auth process works properly.
Initially in my DB design, I use other tables to hold the personal particulars and the files(as I was not ware yet that I am supposed to use the UserProfile to hold additional data for the user). But after going through the auth process and coming across many posts in the stackoverflow, it seems that I should put the data in the UserProfile table.
So my question is,
Can UserProfile contain data both for the auth process(e.g. activation_key key_expire_date) and the specific data e.g. name, country, picture ? (now my UserProfile contains only the data for the auth process)
In this situation, adding new fields now or create anther table to hold the additional info, which one is the better practice for a Django project?
If I add new fields to the UserProfile, would it affect my previous auth functions?(I am a bit concerned about the profile.save() issue..)
Thank you very much for clarifying.
There should be no issue in adding further fields to your UserProfile object - as long as you don't change the data in the columns used by the auth process then you will be fine.
With regards to whether you should split the users 'auth' data from their other data (name, country etc) is really a design question. If you expect that every users profile will always have both kinds of data, then having them together in one table probably makes sense. However, if a user may have 'auth' data, but not necessarily have the other data at any given time, then it probably makes sense to split them into two tables so that the data sets can be maintained separately.

python django implementing subscribers

I have a very simple web application. A student comes in and he creates a note in one of the subjects. Each subject has number of subscribers. Similar to stackoverflow tags, once a tag has been added to a question, the subscribers are notified.
Similary in my app, I want to add subscribers, so once a note is created for a particular subject, all the subscribers of that subject are mailed.
I have my db models as follows -
class Subject(models.Model):
//some vlaues here
class Note(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
author = models.ForeignKey(User)
subject = models.ForeignKey(Subject)
class SubscriptionModel(models.Model):
subject = models.ForeignKey(Subject)
subscriber = models.ForeignKey(User)
There are couple of ways of implementing it -
Once a note is created, a post_save signal can be fired to email all the subscribers of that subjecct.
Something with pub-sub can be done.
Something with rss feeds can be done.
I am not sure what is a scalable approach to implement it, any help or pointers would be appreciated, thanks.
Signal are synchronous, so it will be bad to have mailing done in the save signal because it will slowdown the save process and will not scale.
I suggest to use asynchronous task via queue like django-rq or celery. And you can just put task in the queue in the post_save signal. This approach will scale well and wont interfere with normal site functioning.

Categories

Resources