I am deciding on how to track if a user has seen a post in the timeline or not.
There is Post and Comment model like this.
class Comment(models.Model):
author = models.ForeignKey('UserProfile')
title = models.CharField(max_length=255)
text = models.TextField(null=True, blank=True)
date_created = models.DateTimeField()
post = models.ForeignKey('Post', related_name='comments')
class Post(ContentTypeModel):
title = models.CharField(max_length=255)
group = models.ForeignKey('UserGroup', null=True)
date_updated = models.DateTimeField()
Suggestions about best practices on how to track if post has been seen by particular member of a user group will be nice.
I managed to do as was suggested, i.e. adding m2m relation to track who viewed the post:
class Post(models.Model):
...
date_updated = models.DateTimeField()
viewers = models.ManyToManyField('UserProfile', through='PostViewer')
class PostViewer(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
viewer = models.ForeignKey('UserProfile', on_delete=models.CASCADE, related_name='posts_seen')
last_seen = models.DateTimeField(null=True, blank=True, help_text='Is set when user sees the post in details')
class Meta:
unique_together = ('post', 'viewer')
This way i can compare post.date_updated to viewer.last_seen and get desired filtering on who have seen the update/create of Post.
Related
My models:
class User(models.Model):
id = models.UUIDField(primary_key=True)
first_name = models.Charfield()
class Conversation(models.Model):
id = models.UUIDField(primary_key=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
class Message(models.Model):
id = models.UUIDField(primary_key=True)
conversation = models.ForeignKey(Conversation, on_delete=models.PROTECT, null=False)
text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True, blank=True)
I tried to to order by adding annotate and adding order by. Can some body help me to select all users and order them by the latest message they had. I need user with latest message at to and then accordingly.
Try this
queryset = User.objects.all().order_by(
'-conversation__message__created_at'
).distinct()
A little bit of info. I want the user to be able to view their feed which contains friends' posts, group posts, etc. I am using Django Rest Framework and providing this feed endpoint to a frontend.
My initial thought was just making separate models(tables) per items I needed. Like a UserPost, GroupPost, EventPost, but I feel when trying to consolidate the data it will just be doing a lot of joins and having to stitch together the data.
ex)
class UserPost(models.Model): # GroupPost, EventPost
content = models.TextField(blank=True, default='')
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
# group/event = models.ForeignKey(Group/Event, on_delete=models.CASCADE)
This didn't seem like a good approach in case we wanted to add post type functionality for other models.
My other approach is using intermediate models. Post model is the base and UserPost, GroupPost, EventPost being the intermediate models having a OneToOne relationship to post and GroupPost would have a foreign key (OneToMany) relationship to a Group model.
ex)
class Post(models.Model):
content = models.TextField(blank=True, default='')
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
class UserPost(UUID_PK, models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_uPost', on_delete=models.CASCADE)
class Group(models.Model):
name = models.CharField(max_length=64)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='_groups')
members = models.ManyToManyField(settings.AUTH_USER_MODEL)
class GroupPost(models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_gPost', on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
class Event(models.Model):
name = models.CharField(max_length=64)
about = models.TextField(null=True, blank=True)
event_date = models.DateTimeField(null=True, blank=True)
invited = models.ManyToManyField(settings.AUTH_USER_MODEL)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='_events')
class EventPost(models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_ePost', on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
This approach isn't too bad, but it does require an extra query now.
Then to mimic getting a user's "feed" I would filter like this
users = # friends
groups = # groups of the user
events = # events of the user
posts = Post.objects.filter(Q(created_by__in=users, _gPost__isnull=True, _ePost__isnull=True) | Q(
_gPost__group__in=groups) | Q(_ePost__event__in=events)).distinct().select_related('created_by')
# _gPost__isnull=True and _ePost__isnull=True is exclude the other types of post and only get the "normal" posts.
which just looks awful.
I am unsure if this is a good enough approach or if anyone else has recommended for improving this.
I did look into GenericRelationship and wasn't sure if that would actually make this better. I have a few models that deal with GenericRelationship and for the most part, have been a bit of a pain.
I have a model called Post which has two fields upvotes and downvotes. Now, upvotes, downvotes are ManyToManyField to a User. This is the model:
class Post(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
title = models.CharField(max_length=300)
content = models.CharField(max_length=1000)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
subreddit = models.ForeignKey(Subreddit, on_delete=models.CASCADE)
upvotes = models.ManyToManyField(Profile, blank=True, related_name='upvoted_posts')
downvotes = models.ManyToManyField(Profile, blank=True, related_name='downvoted_posts')
I have to make a query where I have to get all posts sorted by
total (upvotes) - total (downvotes)
I have tried Post.objects.order_by(Count('upvotes')) to get started but there's an error.
Use QuerySet.annotate() first.
Post.objects.annotate(
total_votes=Count('upvotes')-Count('downvotes')
).order_by('total_votes')
An added benefit of annotate() is that every Post in the queryset gets the total_votes attribute, which you can access later with no additional database query or calculation.
I am doing a "Reddit Clone" using django and these are my models:
models.py
class Profile(models.Model):
owner = models.OneToOneField(User, on_delete=models.CASCADE)
dob = models.DateField()
karma = models.IntegerField(default=0)
class Subreddit(models.Model):
owner = models.ForeignKey(Profile, on_delete=models.DO_NOTHING, null=True, blank=True, related_name='subreddits')
name = models.CharField(max_length=100)
title = models.CharField(max_length=100)
description = models.CharField(max_length=500)
subscribers = models.ManyToManyField(Profile, blank=True, related_name='subscriptions')
moderators = models.ManyToManyField(Profile, blank=True, related_name='moderates')
class Post(models.Model):
owner = models.ForeignKey(Profile, on_delete=models.DO_NOTHING)
title = models.CharField(max_length=300)
content = models.CharField(max_length=1000)
votes = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
subreddit = models.ForeignKey(Subreddit, on_delete=models.CASCADE)
class Comment(models.Model):
owner = models.ForeignKey(Profile, on_delete=models.DO_NOTHING)
content = models.CharField(max_length=500)
votes = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
parent_post = models.ForeignKey(Post, on_delete=models.CASCADE)
parent_comment = models.ForeignKey("Comment", null=True, blank=True, on_delete=models.CASCADE)
Now, I tried to delete a user who just made 1 comment. When I tried to delete the user from django admin, I got the following error.
IntegrityError at /admin/auth/user/2/delete/ (1451, 'Cannot delete or
update a parent row: a foreign key constraint fails
(redditdb.redditapp_comment, CONSTRAINT
redditapp_comment_owner_id_fdc65fee_fk_redditapp_profile_id FOREIGN
KEY (owner_id) REFERENCES redditapp_profile (id))')
I did user on_delete=models.CASCADE so I don't know why I am getting this error. Do I need to restructure the foreign keys in my models?
Actually reddit follows a structure where when a parent comment is deleted, its child comments won't get deleted. The deleted parent comment will be dashed or striked out and its child comments are still shown. Knowing this, how do I move forward with my code, should I delete profiles at all?
You Comment is not directly related to User. It is related through Profile. SO when you delete User, its Profile is deleted. which affects the comment, as it becomes dangling.
You can either have
owner = models.ForeignKey(Profile, on_delete=models.CASCADE)
or
owner = models.ForeignKey(Profile, on_delete=models.DO_NOTHING ,null=True)
The error mention that the owner_id of the app's redditapp Comment model references
the id of the app's redditapp Profile model, and the Comment model owner ForeignKey on_delete attribute
is set to models.DO_NOTHIN
DO_NOTHING:
Take no action. If your database backend enforces
referential integrity, this will cause an IntegrityError unless you
manually add an SQL ON DELETE constraint to the database field.
Your best choice is on_delete=models.CASCADE because comments are associated with a specific Profile, if the Profile is deleted its Comments become anonymous with no identifying Profile.
Edit:
Here if the parent comment is deleted the child will not, and the reference to the parent comment will be set to null
class Comment(models.Model):
owner = models.ForeignKey(Profile, on_delete=models.CASCADE)
content = models.CharField(max_length=500)
votes = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
parent_post = models.ForeignKey(Post, on_delete=models.CASCADE)
parent_comment = models.ForeignKey("Comment", null=True, blank=True, on_delete=models.SET_NULL)
The same goes for Post model:
class Post(models.Model):
owner = models.ForeignKey(Profile, on_delete=models.CASCADE)
title = models.CharField(max_length=300)
content = models.CharField(max_length=1000)
votes = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
subreddit = models.ForeignKey(Subreddit, on_delete=models.CASCADE)
Or you can prevent the deletion of the profile all along using models.PROTECT
You have two options:
parent_post = models.ForeignKey(Post, on_delete=models.CASCADE)
or
parent_post = models.ForeignKey(Post, on_delete=models.DO_NOTHING )
I am making a comments section on my webpage and want users to be able to upvote or downvote a comment.
My models are as such:
class Comment(models.Model):
owner = models.ForeignKey(User)
body = models.TextField(null=True, blank=True, max_length=500)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Vote(models.Model):
comment = models.ForeignKey(Comment)
upvote = models.SmallIntegerField(null=True, blank=True, default=0)
downvote = models.SmallIntegerField(null=True, blank=True, default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
When a user posts a comment, I want it to also create a Vote model that is linked to that comment.
I am new to django and programming but from my understanding, I need to create a save hook or something similar?
You can override the save() method of Comment model, ie:
class Comment(models.Model):
...
def save(self, **kwargs):
super(Comment, self).save(**kwargs)
vote = Vote(comment=self)
vote.save()
I suggest you to read the documentation for a better insight.
Consider this code:
class ModelA(models.Model):
name = models.CharField(max_length=30)
#classmethod
def get_new(cls):
return cls.objects.create().id
class ModelB(models.Model):
thing = models.OneToOneField(ModelA, primary_key=True, default=ModelA.get_new)
num_widgets = IntegerField(default=0)
Of course you can use lambda as well, as long as you return integer id of related object. I don't recommend overwritting save method.