I have created a simple project where everyone can create one or more Blog.
I want to use this models for Post and for Comment:
class Post_comment(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField(_('object ID'))
content_object = generic.GenericForeignKey()
# Hierarchy Field
parent = models.ForeignKey('self', null=True, blank=True, default=None, related_name='children')
# User Field
user = models.ForeignKey(User)
# Date Fields
date_submitted = models.DateTimeField(_('date/time submitted'), default = datetime.now)
date_modified = models.DateTimeField(_('date/time modified'), default = datetime.now)
title = models.CharField(_('title'), max_length=60, blank=True, null=True)
post_comment = models.TextField(_('post_comment'))
if it is a comment the parent is not null.
So in most case the text field will contain a little bit of text.
Can I use this model for both Post and Comment ?
Is it a good solution ?
Its technically possible, but sounds like a bad solution.
Most of the queries you'll run are specific to either post or comment (examples: get all posts ordered by date to show on the blog index page, get 5 most recent posts' titles to show on a widget, get 5 most recent comments to show in a "latest comments" widget, get all comments of a specific post, get all the posts that a user has posted, etc). So the cost of having them in the same table is always having the .filter(parent=None) which means less readable code and some performance loss.
Related
I am creating a Blog website where for each blog a user can like or dislike the post.
Now every time user goes on Index page I want to change the like button i.e if user has liked the post then a dislike button otherwise like button.
For this I need to get the variable IsLiked from the views.py file.
This is the Query that I have written.
posts = Post.objects.exclude(users=request.user).select_related('user__people','ProductAvailability').prefetch_related('images_set').annotate(comments_Count = Count('comments_post',distinct=True)).annotate(Count('Likes',distinct=True)).all().order_by('-id')
for post in posts:
if(post.Likes.filter(id=user_id).exists()):
isLiked = True
else:
isLiked = False
Here the problem is that for every post there is a separate query sent to DB.
This is my Blog Post Model ->
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT)
# category = models.ForeignKey(Category, on_delete=models.PROTECT)
ProductAvailability = models.ForeignKey(ProductAvailability, on_delete=models.PROTECT, null=True, blank=True)
title = models.CharField(max_length=255,null=True)
description = models.CharField(max_length=1000,null=True)
Likes = models.ManyToManyField(to=User, related_name='Post_likes')
favourites = models.ManyToManyField(to=User,blank=True,related_name="favourite")
Tag1 = models.CharField(max_length=255,null=True,blank=True)
Tag2 = models.CharField(max_length=255,null=True,blank=True)
Tag3 = models.CharField(max_length = 255, null = True, blank = True)
Tag1_Name = models.CharField(max_length=255,null=True,blank=True)
Tag2_Name = models.CharField(max_length=255,null=True,blank=True)
Tag3_Name = models.CharField(max_length=255,null=True,blank=True)
users = models.ManyToManyField(User, related_name='users_hidden_from_post')
Created_date = models.DateTimeField(auto_now_add=True)
Updated_date = models.DateTimeField(auto_now=True)
PS: Please Ignore the redundant Info in the Post Model
I want to send the User id with the Query and check if individual post is liked by the user or not.
You can annotate the Posts with a condition is_liked that checks if such User appears in the through model of the likes of the Post with an Exists subquery [Django-doc]:
from django.db.models import Exists, OuterRef
posts = Post.objects.exclude(
users=request.user
).select_related(
'user__people', 'ProductAvailability'
).prefetch_related(
'images_set'
).annotate(
comments_Count = Count('comments_post',distinct=True),
Count('Likes',distinct=True),
is_liked=Exists(
Post.Likes.through.objects.filter(
post_id=OuterRef('pk'), user_id=user_id
)
)
).order_by('-id')
The Post objects that arise from this queryset will have an extra attribute named .is_liked that is True if the user_id appears in the Likes for that Post.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: normally the name of the fields in a Django model are written in snake_case, not PascalCase, so it should be: likes instead of Likes.
I have a model to capture comments from a user, DocumentComment. The comments are tied to a particular document in the database, and created/edited/displayed on the page that displays that particular document:
class DocumentComment(Model):
"""
Captures a user's comment about a document
"""
document_id = models.ForeignKey(Document, on_delete=models.CASCADE, verbose_name="document file name", related_name='document')
user = models.ForeignKey(User, on_delete=models.CASCADE)
comment = models.TextField('comment', blank=True)
moderated = models.BooleanField(default=False)
added_to_description = models.BooleanField(default=False)
marked_for_deletion = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="date created")
updated = models.DateTimeField(auto_now=True, editable=False, verbose_name="last update")
I now find I need to record comments based on other objects displayed on different pages in the site, e.g. albums (collections of document) is a page and word clouds (derived from all the words in various documents) is another page. Since these new comments are not tied to one document, but a collection of documents, I don't think they should be added to the DocumentComment model for every document_id in the collection. I wouldn't necessarily want the comments on a collection to be displayed on the page with one of the documents in that collection.
Which approach is better (django-yy or pythonic or idiomatic or by whatever standard you choose):
create separate models as above for each display object (i.e. word clouds, albums, maybe more?)
keep adding foreign keys to the various objects to the DocumentComment model and adding on_delete=models.SET_NULL, blank=True, null=True to each of the foreign key fields
Also, are there some hidden gotchas about option #2 that I have not thought of?
Thanks!
Mark
Model Abstraction is the solution
Class Comment(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
comment = models.TextField('comment', blank=True)
moderated = models.BooleanField(default=False)
added_to_description = models.BooleanField(default=False)
marked_for_deletion = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="date created")
updated = models.DateTimeField(auto_now=True, editable=False, verbose_name="last update")
Class Meta:
abstract = True
Class DocumentComment(Comment):
document_id=Models.ForeignKey()
Class AlbumComment(Comment):
album_if = Models.ForeignKey()
I'm trying to create a image site with a similar form for adding posts as on Imgur. A post should be made up of unlimited number of blocks of various types (text, image, video) that create finished blog post.
User chooses with which block he wants to start (maybe upload an image) and then adds another block by clicking a button.
I can't figure out a sensible model for blocks that would make up a single post.
This is my Post model:
class Post(models.Model):
author = models.ForeignKey('auth.User')
text = models.TextField() #just a placeholder until blocks work
created_date = models.DateTimeField(
default=timezone.now)
isWaiting = models.BooleanField(default=True)
isLocked = models.BooleanField(default=False)
views = models.IntegerField(default=0)
tags = TaggableManager(help_text="")
I don't know if I should define separate models for textblock, imageblock and videoblock (all with ForeignKey to Post model) or if there's a better solution.
I thought of a universal model that would store a TextField (for text written by the user) and a FileField (for image and video upload) used for every block type but one of the Fields in every record would always be empty (user can only write text or upload a file per block) and it seems like a "waste of space".
I appreciate any ideas for solving this problem.
If anyone comes across a similar problem I chose a universal content block design with many fields (some are empty depending on a block type, ex. media is empty when adding text block). It was easier for me to implement and later add necessary JS for dynamically adding more PostBlocks to Post.
Relevant models:
class Post(models.Model):
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
created_date = models.DateTimeField(default=timezone.now)
views = models.IntegerField(default=0)
tags = TaggableManager(help_text="")
class PostBlock(models.Model):
POST_TYPE_CHOICES = (
('TXT', 'Text'),
('IMG', 'Image'),
('VID', 'Video'),
)
postid = models.ForeignKey('Post', on_delete=models.CASCADE)
text = models.TextField(max_length=1024, blank=True)
media = models.FileField(upload_to=content_path, blank=True, validators=[
validate_file_extension])
contenttype = models.CharField(
max_length=3,
choices=POST_TYPE_CHOICES,
default='IMG',
)
order = models.IntegerField(default=0)
One Post can have as many PostBlocks as you need.
I have a fairly simple Django set up for a forum, and one of the most basic models is this, for each thread:
class Post(models.Model):
created = models.DateTimeField(auto_now_add=True)
last_reply = models.DateTimeField(auto_now_add=True, blank=True, null=True)
username = models.ForeignKey(User, related_name="forumuser")
fixed = models.BooleanField(_("Sticky"), default=False)
closed = models.BooleanField(default=False)
markdown_enabled = models.BooleanField(default=False)
reply_count = models.IntegerField(default=0)
title = models.CharField(_("Title Post"), max_length=255)
content = models.TextField(_("Content"), blank=False)
rating = models.IntegerField(default=0)
followers = models.IntegerField(default=0)
ip_address = models.CharField(max_length=255)
def __unicode__(self):
return self.title
def get_absolute_url(self):
return "/post/%s/" % self.id
Then we have some replies:
class PostReply(models.Model):
user = models.ForeignKey(User, related_name='replyuser')
post = models.ForeignKey(Post, related_name='replypost')
created = models.DateTimeField(auto_now_add=True)
content = models.TextField()
ip_address = models.CharField(max_length=255)
quoted_post = models.ForeignKey('self', related_name='quotedreply', blank=True, null=True)
rating = models.IntegerField(default=0)
reply_order = models.IntegerField(default=1)
Now, currently there just over 1600 users, 6000 Posts, and 330,000 PostReply objects in the db for this setup. When I run this SQL query:
SELECT * FROM `forum_post` LIMIT 10000
I see that Query took 0.0241 sec which is fine. When I browse to the Django admin section of my site, pulling up an individual Post is rapid, as is the paginated list of Posts.
However, if I try and pull up an individual PostReply, it takes around 2-3 minutes to load.
Obviously each PostReply admin page will have a dropdown list of all the Posts in it, but can anyone tell me why this or anything else would cause such a dramatically slow query? It's worth noting that the forum itself is pretty fast.
Also, if it is something to do with that dropdown list, has anyone got any suggestions for making that more usable?
Try to add all foreign keys in raw_id_fields in admin
class PostReplyAdmin(ModelAdmin):
raw_id_fields = ['user', 'post', 'quoted_post']
This will decrease page's load time in change view. The problem is that django loads ForeignModel.objects.all() for each foreign key's dropdowns.
Another way is to add foreign keys in autocomplete_fields (docs) in admin
class PostReplyAdmin(ModelAdmin):
autocomplete_fields = ['user', 'post', 'quoted_post']
As pointed by #Andrey Nelubin the problem for me was indeed in the page loading all related models for each foreign key's dropdown. However, with autocomplete_fields selects are turned into autocomplete inputs (see figure below), which load options asynchronously.
These are my models:
class Comment(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField(_('object ID'))
content_object = generic.GenericForeignKey()
user = models.ForeignKey(User)
comment = models.TextField(_('comment'))
class Post(models.Model):
title = models.CharField(_('name'), max_length=80)
creator = models.ForeignKey(User, related_name="created_posts")
created = models.DateTimeField(_('created'), default=datetime.now)
body = models.TextField(_('body'), null=True, blank=True)
Now in my views.py I get a post with this istruction:
post = get_object_or_404(Post, id=id)
At this point in my views.py, what is the most efficient query ( with ORM ) to get all comments of that post ?
You should define comments = generic.GenericRelation(Comment) on the Post, to give you easy access from Post to Comment. Once you've done that, it's a simple backwards relationship:
comments = post.comments.all()
Note that this isn't really a question of efficiency. Getting all the related items via a backwards generic relationship will always incur at most two queries - one to get the relevant ContentType, which is automatically cached on first look-up, and once to get the actual items.
If you had asked how to get all the comments for multiple posts as efficiently as possible, I'd point you to my blog for a good technique, but since you haven't I won't because that would just be blog-whoring.