How to make Django comments use select_related () in the "user" field?

I am using django comment frames . All comments are posted by authenticated users. Next to the comment, I show some user profile data using {{ comment.user.get_profile }}

 {# custom comment list templates #} <dl id="comments"> {% for comment in comment_list %} <dt id="c{{ comment.id }}"> {{ comment.submit_date }} - {{ comment.user.get_profile.display_name }} </dt> <dd> <p>{{ comment.comment }}</p> </dd> {% endfor %} </dl> 

The problem is that django comment requests do not use select_related() , and for 100 comments I get 101 hits on the database.

Is there a way to make the django comments framework to select a user profile for each comment at a time?

+6
source share
3 answers

I tested the rendering of 100 comments for an object tagged with {% get_comment_list %} by default, and django executed 200 comments related to the request to list comments + user profile because ...

  • Comment.__unicode__ actually calls Comment.user if user_id exists. +1 request
  • get_profile +1 request

Oh!

I went from 203 requests in ~ 25 ms to 3 in ~ 2 ms.

Fill in the comment_list itself

I would suggest creating comment_list QuerySet yourself using the appropriate select_related() calls. If it is used frequently, create a utility function called from your other views.

 def get_comments_with_user_and_profile(obj): content_type =ContentType.objects.get_for_model(obj) return (Comment.objects .filter(content_type=content_type, object_pk=obj.id) .select_related('user__profile')) 

If you want the entire infrastructure to behave this way ... You must have a monkey patch.

This is not something I would do lightly. There are other ways to solve this particular problem, but you asked "at a time."

Put this somewhere in your INSTALLED_APPS models.py files. I actually have a monkey_patch application for changing the length of django.contrib.auth.User.username and such (which is the last resort, unlike here).

 from django.contrib.comments.models import Comment from django.contrib.comments.managers import CommentManager class CommentManager(CommentManager): def get_query_set(self): return (super(CommentManager, self) .get_query_set() .select_related('user__profile')) Comment.add_to_class('objects', CommentManager()) 

Gotchas with profiles and select_related ()

Note that the UserProfile class requires OneToOneField to User with related_name equal to what you pass select_related() . In my example, this is profile , and you need django 1.2+. I remember how I stumbled on this before.

 class UserProfile(models.Model): user = models.OneToOneField(User, related_name='profile') # example to use User.objects.select_related('profile') 
+10
source

Assuming you have this setup:

 class UserProfile(models.Model): user = models.ForeignKey(User, related_name='profile') ... 

You can use the following related select: Comments.objects.select_related('user__pk','user__profile__pk') and it should do what you want.

You will need to expand the scope of the comments. It is pretty simple. Basically create your own comment app. You can look at django-threadedcomments for inspiration (and, in fact, in a way, this is already the best implementation to use anyway).

Here, the code can be inserted into the django-threaded comment application to make sure that it always uses the associated select (in models.py):

 class RelatedCommentManager(CommentManager): def filter(self, *args, **kwargs): return super(RelatedCommentManager, self).select_related('user__pk','user__profile__pk').filter(*args, **kwargs) def exclude(self, *args, **kwargs): return super(RelatedCommentManager, self).select_related('user__pk','user__profile__pk').exclude(*args, **kwargs) def all(self) return super(RelatedCommentManager, self).select_related('user__pk','user__profile__pk').all() 

and replace

  objects = CommentManager() 

from

  objects = RelatedCommentManager() 

Follow the instructions for integrating threads into your application.

Then in the template, I think you will have to reference .profile instead of .get_profile .

Perhaps Django will automatically detect this value, so get_profile will not generate another db hit as long as .profile is available.

+4
source

You cannot use select_related () in this example because the user is the profile foreign key, and not vice versa. To avoid using a cache (which is probably the best option), you can create a proxy model for a comment with a foreign key to your profile model. then you can write:

 {{ comment.submit_date }} - {{ comment.user.profile.display_name }} 
0
source

Source: https://habr.com/ru/post/900025/


All Articles