Is this a good approach to structure my models? - django

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.

Related

How to make a query across multiple models in Django

I'm using Django and I want to know how to get objects through 3 models
These are my models
class Participant(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
is_leader = models.BooleanField(default=False)
team = models.ForeignKey(Team, on_delete=models.CASCADE, null=True, related_name="participants")
application_date = models.DateField(auto_now_add=True, null=True)
resolution_date = models.DateField(null=True, blank=True)
accepted = models.BooleanField(default=False)
class Team(models.Model):
name = models.TextField(default="")
is_public = models.BooleanField(default=False)
institution = models.ForeignKey(Institution, on_delete=models.CASCADE, null=True, related_name='teams')
campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE, null=True, related_name='teams')
class Campaign(models.Model):
name = models.TextField(default="")
description = models.TextField(default="")
initial_date = models.DateTimeField(auto_now_add=False, null=True, blank=True)
end_date = models.DateTimeField(auto_now_add=False, null=True, blank=True)
qr_step_enabled = models.BooleanField(default=True)
image_resolution = models.IntegerField(default=800)
sponsor = models.ForeignKey(Sponsor, on_delete=models.CASCADE, null=True, related_name='campaigns')
I have the user through a request, and I want to get all campaigns of that user.
I tried doing it with for loops but I want to do it with queries
this is what I had:
user = request.user
participants = user.participant_set.all()
for participant in participants:
participant.team.campaign.name
is there a way to make a query through these models and for all participants?
A user can have many participants, and each participant has a Team, each team has a campaign
The best way is to merge the two modela Team and Campaign in one model.
Something as simple as this should work:
Campaign.objects.filter(team__participant__user=request.user)
The Django ORM is smart enough to follow foreign key relationships in both directions.
Thanks to Daniel W. Steinbrook to guide me to the answer, I had to do this to get the query:
Campaign.objects.filter(teams__participants__user__exact=request.user)

A better way to orgainize django-channels one to one private chat models?

I want to model the database table of a django channels private chat in the most effective way possible so that retrieving the messages would not take a lot of time.
For that I have create a one to one room since every two users should have their unique room.
And I created a separate message table with a foreign key relation with the (oneToOneRoom) table.But what bothers me is that since the sender and the receiver of the message can only be one of the two users of the room I do not want set (sender and receiver) as foreign Key to the users table. Can I have a better implementation of that. Or is their any other way of modeling those tables. Any help would be highly appreciated.
Here is the models.py file.
class singleOneToOneRoom(models.Model):
first_user = models.ForeignKey(
User, on_delete=models.SET_NULL, related_name="first_user", blank=True, null=True)
second_user = models.ForeignKey(
User, on_delete=models.SET_NULL, related_name="second_user", blank=True, null=True)
room_name = models.CharField(max_length=200, blank=True, unique=True)
def __str__(self):
return f"{self.first_user}-{self.second_user}-room"
class Meta:
unique_together = ["first_user", "second_user"]
class messages(models.Model):
room = models.ForeignKey(
singleOneToOneRoom, on_delete=models.CASCADE, related_name="messages")
message_body = models.TextField()
sender = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="msg_sender")
receiver = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="msg_receiver")
date_sent = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.sender}_to_{self.receiver}"
The design seems ok. You can add more constraints to be sure, that message belongs to the right users.
Here my suggestions:
Make first_user and second_user NOT NULL fields.
Make sure, that you have exactly 1 room for users pair (first_user.id < second_user.id)
Make sure, that (receiver, sender) pair equals (first_user, second_user) pair
class singleOneToOneRoom(models.Model):
first_user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="first_user", null=False)
second_user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="second_user", null=False)
class Meta:
unique_together = ["first_user", "second_user"]
constraints = [
models.CheckConstraint(check=Q(first_user__id__lt=F('second_user__id')), name='unique_user_pair'),
]
class messages(models.Model):
room = models.ForeignKey(
singleOneToOneRoom, on_delete=models.CASCADE, related_name="messages")
message_body = models.TextField()
sender = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="msg_sender")
receiver = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="msg_receiver")
class Meta:
constraints = [
models.CheckConstraint(
check=Q(sender=F('singleonetooneroom__first_user') & Q(receiver=F('singleonetooneroom__second_user'))
| Q(sender=F('singleonetooneroom__second_user') & Q(receiver=F('singleonetooneroom__first_user')),
name='valid_sender_and_receiver'
),
]

Generic model that can be used by several other models but only one at a time

I'm trying to make a Description model that can be used by several different models.
But the Description model can only have one at a time.
Lets say that i have these two models.
class Account(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
birthdate = models.DateField()
phoneNumber = models.CharField(max_length=20)
class CompanyProfile(models.Model):
companyName = models.CharField(max_length=50)
VAT = models.CharField(max_length=35, unique=True)
I want both to have multiple descriptions. But the description object can only be linked to one model. A (dirty) solution that i found was this.
class Description(models.Model):
title = models.CharField(max_length=100)
text = models.CharField(max_length=2500, null=True, blank=True)
account = models.ForeignKey(Account, on_delete=models.CASCADE, null=True, blank=True)
company = models.ForeignKey(CompanyProfile, on_delete=models.CASCADE, null=True, blank=True)
In normal python you would just be able to have a list of description. But in django, this isn't possible. I've thought about using generic relations but that seems to work differently.
Why I want to do it like this?
Because I want to expand Description by using inheritance and I want to be able to use all those types of 'Descriptions' for multiple classes.
Django has got Generic ForeignKey
But I feel a better way to implement this is to add OneToOne field in each model mapping to Description model
description= models.ForeignKey(Description, on_delete=models.SET_NULL, null=True, blank=True)

Best way to add comment field in Django Rest Framework

Currently I am creating a site that I would like users to be able to comment in. I am just having a hard time wrapping my head around how to model the models so to speak. The way I have it, I believe that the comments aren't connecting themselves to the main article, I believe they just get added and sunk into the abyss. What's the best way to connect the models? Should I just not have a separate model for the comments? This is more of a hypothetical question than a how to code question. Currently this is the way I have it.
class Comments(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
comment = models.CharField(null=True, blank=True, max_length=5000)
date = models.TextField(blank=True, null=True)
username = models.CharField(null=True, blank=True, max_length=20)
class Article(models.Model):
title = models.CharField(max_length=250, primary_key=True)
content = models.TextField(blank=True, null=True)
usedfor = models.TextField(blank=True, null=True)
url=models.CharField(max_length=200, null=True)
username = models.CharField(max_length=50, null=True, blank=True)
article_programming_language = models.ForeignKey(ProgrammingLanguage, on_delete=models.CASCADE, blank=True, null=True)
score = models.IntegerField(max_length=5, null=True, blank=True)
article_framework = models.ForeignKey(Framework, on_delete=models.CASCADE, related_name="article_framework", blank=True, null=True)
date_added = models.IntegerField( max_length=10, blank=True, null=True)
article_comments = models.ForeignKey(Comments, on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.title
It seems like that you have got all the things you need in your class.Here's some opinions.
Comments should be a separate model.
Each comment should be addressed to a specific user (or an anonymous user).A user could have many comments, but a comment should only be created by one user.Adding a ForeignKey inside comments as [Bruno Monteiro] advised could help.
Each comment should be addressed to a specific article.This could also be an One-To-Many Relationship(An article could have many comments,but a comment only belongs to a specific article).
'Best design' is too huge to answer.The design that meets your need is the best.So when you want to find a 'best design', just ask yourself what functions it will provide, how the comments should work,etc.
This is my suggestion,
Create a comment model,
Add user model with a f.k. user = models.ForeignKey(User, on_delete=models.CASCADE,) # You can add null and blank as per your requirement
Add article model with a f.k., article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='articlecomment')
Add date with auto_now_add = True
Here users can add any number of comments they want.

Best way to make "viewed" attribute for messages inside user group?

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.