How to make Django comments use select_related() on "user" field? - django

I'm using django comments frameworks. All the comments are posted by authenticated users. Near the comment, I'm showing some user profile info 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>
Problem is that django's comment queries does not use select_related() and for 100 comments I get 101 hit on the database.
Is there a way to make django comments framework to select user profile for each comment in one go?

I tested rendering 100 comments for an object with the default {% get_comment_list %} tag and django did 200 comment related queries to list the comments + user + profile because...
Comment.__unicode__ actually calls Comment.user if a user_id exists. +1 query
get_profile +1 query
Ouch!
I went from 203 queries in ~25ms to 3 in ~2ms.
Populate comment_list yourself
I would highly suggest building the comment_list QuerySet yourself using the appropriate select_related() calls. If it's used often, 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 framework to behave this way... You'll have to monkey patch.
It's not something I would do lightly. There are other ways around this specific problem but you did ask "in one go".
Put this somewhere in your INSTALLED_APPS models.py files. I actually have a monkey_patch app for modifying django.contrib.auth.User.username lengths and such (which is a 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 your UserProfile class needs a OneToOneField to User with a related_name equal to what you pass to select_related(). In my example it's profile and you need django 1.2+. I recall stumbling on that before.
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
# example to use User.objects.select_related('profile')

Assuming that you have a setup like so:
class UserProfile(models.Model):
user = models.ForeignKey(User, related_name='profile')
...
You can use the following select related: Comments.objects.select_related('user__pk','user__profile__pk') and that should do what you want.
You'll have to extend the comments framework. This is fairly straightforward. Basically, create your own comments app. You can look at django-threadedcomments for inspiration (and, actually, in some ways it's already a better implementation to use anyway).
Here's code you can insert into the django-threaded comments app to make sure it always uses the select related (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()
with
objects = RelatedCommentManager()
Follow the instructions for integrating threadedcomments into your app.
Then, in the template, I think you'll have to reference .profile instead of .get_profile.
It may be that Django automatically factors this in, so get_profile will not generate another db hit so long as .profile is available.

You can't use select_related() in this example, because User is foreign key of profile, not vice versa.
To avoid using cache (which is probably the best option) you could create proxy model for Comment with foreign key to your profile model. then you could write:
{{ comment.submit_date }} - {{ comment.user.profile.display_name }}

Related

How to pass dynamic filtering options in django_filters form instead of all model objects?

I have the following structure of the project (models):
Company (based on Groups) <-- Products (foreignKey to Company) <-- Reviews (foreignKey to Products).
Inside template i want to give a user an opportunity to filter reviews by products, but when using django_filters it shows corrects queryset of reviews (related only to company's products) but in filter form dropdown options i can see all products of all companies (even those which are not presented on the page), for example for Company X i see on my page only Cactus and Dakimakura reviews, but in filter form i can select Sausage (but i shouldnt, because its product from another company).
For now it's all looks like this:
#View
def reviewsView(request):
context = {}
user = request.user
company = user.company
products_qset = ProductModel.objects.filter(company=company)
reviews_objects = ReviewModel.objects.filter(product__in=products_qset)
filter = ReviewFilter(request.GET, queryset=reviews_objects)
context['filter'] = filter
return render(request, 'companies/reviews.html', context)
#Filter
class ReviewFilter(django_filters.FilterSet):
class Meta:
model = ReviewModel
fields = [
'product',
'marketplace',
'operator',
'state'
]
#Template
<form action="" method="get">
{{ filter.form.as_p }}
<input type="submit" name="press me" id="">
</form>
<div class="review-list">
{% for i in filter %}
{{i.product}}, {{i.marketplace}} etc.
{% endfor %}
I've done quite a lot of research on django_filters docs and this question seems like duplicate, but i can't fully understand what and why is happening to init in this answer and how to correctly rewrike class ReviewFilter so filter instance would do something like this (in View):
filter = ReviewFilter(request.GET, queryset_to_dispplay=MyQueryset, queryset_to_display_filtering_options=MyQueryset). For now its surely happening because ReviewFilter Meta points to the all table (ReviewModel) objects, instead of filtered beforehands.
I also trying to apply pagination on this page (i cut this from question code for "shortness") and after i will be able to implement filtering i would like to merge those two somehow, but if what i want is not possible - please point me to the right direction (for example: 'you can do this by writing your very own filtering system using some js, no need to use django_filters' or 'with angular.js/view.js/somethingelse you can have it all from the box').

django custom forms with models, also with 'choices' as values, how should I approach this problem?

django forms is giving me a pretty bad headache...
I've been struggling with this for 2 hours now.
I wanted to make a custom form for a model to be able to add objects to it and also be able to move the fields as I want to (instead of using form.as_p (or form|crispy), I wanted to put the entire form in a grid-like table using a custom pattern, not only putting one under another) but whenever I put a choicefield (which is either a choicefield or a foreignkey) their values are empty and also, 3 out of 4 formfields are actually shown, one remains the default.
It contains fields='__all__' with 3 choicefields and 1 floatfield each with a label, nothing more.
To show the forms in html I used
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{{ field.help_text }}
{% endfor %}
which works well. Am I trying to solve the problem in a wrong way? I'll come back to this topic tomorrow, I'll need some rest now but I don't understand why passing a choices=TheModel # or # choices=TheModel.ojbects.all() breaks the entire thing.
Is there a website or a youtube channel that shows some solutions to those problems?
I lokoed up a bunch of sites and videos but they never access foreign keys as values to forms(dropdowns), never make grouped dropdowns (which I made and is working without custom forms).
Small update, I'm trying with 'labels' and '|as_crispy_field' tags but "exptype" is not changing. Everything else does. and its name is matched too.
https://imgur.com/a/CvP5565
( multiple screenshots attached )
Choices needs to be a tuple like this:
[
('CHOICE_ONE', 'choice_one')
]
So, you could create the choices list like this (Lets assume TheModel has a name field.)
choices = [(i.id, i.name) for i in TheModel.objects.all()]
The second value will be displayed to the user, the first one will be set in the database.
You could use a ModelChoiceField:
class FooMultipleChoiceForm(forms.Form):
foo_select = forms.ModelMultipleChoiceField(queryset=None)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['foo_select'].queryset = ...
https://docs.djangoproject.com/en/4.1/ref/forms/fields/#modelchoicefield

How to make Django admin honor grammatical cases in languages like Polish?

Django allows overwriting verbose_name and verbose_name_plural of a Model, which allows us to specify correct plural forms of Model names for non-English languages (for example Kandydaci as a plural of Kandydat instead of the default Kandydats which looks weird in Polish).
However, this is far from enough for languages with grammatical cases. For example, Django Admin gleefully displays us something like that:
(Where Zaznacz kandydat do zmiany stands for Select kandydat to change - Kandydat is a name of a Model)
This is incorrect. In this sentence the model name should have been displayed in accusative case, which is kandydata. However, I cannot simply specify that verbose_name of this Model is Kandydata, since would also affect all places where nominative case is expected instead - I've found one such place so far, and this is the heading of the table column:
This is incorrenct since the table heading should be called Kandydat not Kandydata.
How to fix this?
The string 'Select <verbose_name> to change' is declared inside the admin's main view
self.title = title % force_text(self.opts.verbose_name)
as a {{ title }} template variable
{% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}
So, touching the built-in view is out of question. But there is another way of doing it! You see, Django Admin templates are full of {% block %}s acting as placeholders in order to override them. Read more at the official docs about overriding Admin templates.
So, to your question.
Under your project's root directory create a directory templates (if it doesn't exists already).
Under my_project/templates/ create another one, admin.
Under my_project/templates/admin/ create a file change_list.html.
Inside my_project/templates/admin/change_list.html put these:
{% extends 'admin/change_list.html' %}
{% load myutils_filters %}
{% comment %}
You have to create an app that will hold all common tags/filters
that are re-usable across your entire project.
The model class itself is passed as cl.model
{% endcomment %}
{% block content_title %}{{ block.super|to_accusative:cl.model }}{% endblock %}
Inside your myutils/templatetags/myutils_filters.py file put these:
from django import template
from django.utils.html import format_html
from django.utils.encoding import force_text
register = template.Library()
#register.filter()
def to_accusative(value, model):
verbose_name = model._meta.verbose_name # declared in Meta
new_name = force_text(model.accusative case()) # a custom class method (lives in your Model)
return format_html(value.replace(verbose_name, new_name))
Finally, under your app's models.py, under each model class define a classmethod method like this:
from django.db import models
from django.utils.translation import ugettext_lazy as _
class MyModel(models.Model):
# model fields here
def __str__():
return self.a_field
class Meta:
verbose_name = 'verbose name'
verbose_name_plural = 'verbose name plural'
# accusative_case = 'accusative name case' # BAD. Raises error. Implement a class method intead ;)
#classmethod
def accusative_case(cls):
# You'll define the Polish translation as soon as you run makemessages
return _('Name in english of the accusative case')
Run manage.py makemessages, manage.py compilemessages, reload your browser and voila!
Note: The above may look a little hackish but it works brilliant. Tested locally and works.

Django - Checkboxes & ManytoMany relationships in TemplateView

I have a app where users can register their company and then select a number of settings from a list. Both the company and services are different models.
class Company(models.Model):
name = models.CharField(max_length=100)
(...)
class Service(models.Model):
name = models.CharField(max_length=100)
linked_companies = ManyToManyField(Company, blank=True)
What I want is to have a large list of services, with checkboxes behind their names, so the owner can quickly select the services that he wants to connect to his model. This used to be done through the admin interface, but due popular demand this feature is moved to 'the front'.
The problem is that I do not know how to fit this into the traditional (generic) view/form combinations that we' ve been using so far, since two different models are involved.
I am trying a more custom solution, but have hit a wall and I am wondering if you could help me. I have created a html page that should display both the list of services and a 'save' button.
<form action="." method="POST" class="post-form">{% csrf_token %}
<ul>
{% recursetree services %}
<li>
<label><input type="checkbox" name='service' value={{ node.pk }}><h3>{{ node.name }}</h3></label>
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
<button type="submit" class="save btn btn-default">Add Selected
</button>
</form>
I am using the following ModelForm:
class FacetForm(forms.ModelForm):
class Meta:
model = Services
fields = ['linked_tenants', 'name']
widgets = {
'linked_tenants' : CheckboxSelectMultiple()
}
This HTML page seems to work as intended, showing a long list of services with checkboxes after their names.
However, I have trouble creating a function view. Together with a collegue the following view was created
class FacetList(TenantRootedMixin, TemplateView):
def get_context_data(self, **kwargs):
d = super(ServiceList, self).get_context_data(**kwargs)
d['services'] = Services.objects.all()
d['current_company'] = self.context.company.id
return d
def form_valid(self, *args, **kwargs):
return super(ServiceList, self).form_valid(*args, **kwargs)
This view works in the sense that it shows all of the relevant information (with the checkboxes). If I change the query to filter the services by 'company id'. the view works as desired as well.
The problems I have revolve around the fact that pressing 'save'. crashes the program, throwing the following error.
'super' object has no attribute 'post'
Our program works mostly through generic classbased views and modelforms, so we have relativly limited experience with creating our own custom solutions. By my own estimation the problem seems to be twofold:
The view is probably not configured right to process the 'post' data
It is questionable if the data will be processed to the database afterwards.
Though are 'sollution' is currently flawed, are we looking in the right direction? Are we on the right way to solve our problem?
Regards
I believe you are on the right track. What I would suggest is to not be afraid to move away from generic views and move toward a more custom solution (even if you are inexperienced with it.)
The first routine that comes to my mind would be as follows:
gather all the id's that were checked by the user into a list from request.POST
Update the appropriate object's M2M field to contain these new id's.
Save the fore-mentioned object.
[Edit]
One thing I have trouble with is gathering the ID' s from the request.POST. Could you provide me with an example on how to do this?
Sure, from your HTML file I see you are creating inputs with name=service. That leads me to believe you could do something like:
ids = request.POST.get('service')
but to teach you how to fish rather than giving you a fish, you should try to simply:
print request.POST.items()
This will return and print to the console everything that was posted from your form to your view function. Use this to find out if you are getting a list of id's from the template to the server. If not, you may have to re-evaluate how you are building your form in your template.
Your first point is correct: TemplateView has no "post" method defined and that is why you get the error message when you call super().form_valid. You must either define it yourself or use a CBV which has a post method that you can override (e.g. UpdateView)
And I also believe that your second point is correct.
You would need to use an UpdateView to use the built in functionality (or CreateView).
I had a similar problem to solve (selecting values from many-to-many fields in the front-end) and I ended up with doing it "by hand" because I could not get it to work with CBV. "by-hand" => parse the values from the form, update the database, return HttpResponse
You might want to look at ModelFormSets:
https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/#model-formsets
Hope this helps!
Alex

How to display multiple forms of a single model in Django templates?

I have this model Note:
class Note(models.Model):
category = models.ForeignKey(Category)
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=40)
text = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
And I want to display this form:
class NoteEditForm(forms.ModelForm):
class Meta:
model = Note
fields = ('title', 'text')
in a template, but I want it to appear for each existing Note object in the database (it has to be that way). I've done something like that but then I hardcoded the form, pointing to the edit view URL with each object pk as a parameter; but I'm sure it has to be a clearer way, just I haven't found it. Could you guys help me with that? Thanks!
The easiest way to do this is to use a formset (also see model formsets).
For your Note model and NoteEditForm you could do something like this. You'd usually put this wherever you've defined your NoteEditForm but it can go in another file, such as views.py.
from django.forms import modelformset_factory
NoteEditFormSet = modelformset_factory(Note, form=NoteEditForm)
Using NoteEditFormSet in a view and template is almost the same as using a regular form, but there are a few differences to be aware of if you want to do anything complicated so have a look at the docs (view and template). If that's not clear enough, add a few details of what you're trying to do in your view and template and I'll try to help.
By default the formset will use Note.objects.all() as its queryset, which is what you say you want, but you can change that (details are covered in the docs).
Update:
To save an individual Note with an AJAX request I would add a second view to handle those requests. So if your formset for all Notes is served by a view at /notes/, you could add a view to handle your AJAX request at /notes/<note_id>/ (obviously just an example, adjust to fit your URL structure).
Then your JS on the /notes/ page is responsible for serializing the data for a single note and making the request to /notes/<note_id>/ (remember the CSRF token).
The HTML inputs generated for the formset have their IDs prefixed with something like id_form-<number>- and there are hidden inputs containing Note primary keys which will let you work out which ID prefix applies to each note.
I would think about doing it like this
{% for note in Notequeryset %}
<form action={% url 'url_name_to_form' pk={{note.pk}} %}>
{{form.as_p}}
</form>
{% endfor %}
Let me know what you think