I use Django 1.8.4 with Python 3.4
I have a model for tournaments that defines a method which returns a string if a subscription is forbidden.
class Tournament(models.Model):
name = models.CharField(max_length=200, null=True, blank=True)
subscriptions = models.ManyToManyField('ap_users.Profile')
is_subscription_open = models.BooleanField(default=True)
# ...
def why_subscription_impossible(self, request):
if not request.user.profile.is_profile_complete():
return 'Your profile is not complete'
elif not self.is_subscription_open:
return 'Subscriptions are closed'
elif <another_condition>:
return 'Another error message'
return None
I want to display the list of tournaments, using a generic ListView, and I want to use the result of the method to modify the way it is displayed:
<table class="table">
<thead>
<td>Tournament</td>
<td>Subscription</td>
</thead>
{% for tournament in tournament_list %}
<tr>
<td>{{ tournament.name }}</td>
<td>
{% if tournament.why_subscription_impossible %}
{{ tournament.why_subscription_impossible }}
{% else %}
Subscribe
{% endif %}
</td>
</tr>
{% endfor %}
</table>
The view is a class based generic view inherited from generic.ListView.
class IndexView(generic.ListView):
template_name = 'ap_tournament/index.html'
def get_queryset(self):
return Tournament.objects.all()
The shown solution doesn't work, because I need to pass the current request, to get information about logged user. So I tried to add the result of the method to a context in the view
class IndexView(generic.ListView):
template_name = 'ap_tournament/index.html'
def get_queryset(self):
return Tournament.objects.all()
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
additional_ctx_info = []
for tournament in self.get_queryset():
additional_ctx_info.append({
'reason_to_not_subscribe': tournament.why_subscription_impossible(self.request)
})
context['subscr_info'] = additional_ctx_info
return context
Obviously, this doesn't work too. I don't know how to access to the subscr_info[n] with n the current index in the tournament_list. I know the forloop.counter0 to get the index, but I can't use it in the template (or I don't know how). I tried :
{{ subscr_info.forloop.counter0.reason_to_not_subscribe }}
{{ subscr_info.{{forloop.counter0}}.reason_to_not_subscribe }}
I also tried to annotate the QuerySet in get_queryset() view method and read about aggregate(), but I feel that works only with operations supported by the database (AVG, COUNT, MAX, etc.).
I also feels that using a filter or a template tag will not work in my case since I need to use the result of the method in a if tag.
Is there a better solution or a completely diffferent method to achieve what I want ?
In your view, you could also do:
tournaments = self.get_queryset()
for tournament in tournaments:
tournament.reason_to_not_subscribe = tournament.why_subscription_impossible(self.request)
Then add tournaments to the context.
You have to create tournament_list in your views.py file, and set it depending on whether the user is logged and has the corresponding permissions.
If you need to count something, you can create the following Counter class :
class Counter:
count = 0
def increment(self):
self.count += 1
return ''
def decrement(self):
self.count -= 1
return ''
You can then use it in your templates by calling {{ counter.increment }} and {{ counter.count }}. ({{ subscr_info.{{counter.count}}.reason_to_not_subscribe }} and don't forget to place {{ counter.increment }} in your loop.
However, a nicer workaround I used was to create a dictionnary containing both the main element and the additional information, i.e.
ctx_info = []
for tournament in self.get_queryset():
ctx_info.append({
'tournament': tournament
'reason_to_not_subscribe': tournament.why_subscription_impossible(self.request)
})
and then loop on ctx_info. It's cleaner, but I however do not know how this can be implemented within a ListView (which I never used)
By the way, your template contains {{ why_subscription_impossible }} instead of {{ tournament.why_subscription_impossible }}, I don't know if it was intended...
Related
So, I try to display a list of items from database, but after calling return render(...) it behaves as if there were no objects in the database. I am new to django and after a second day of trial and error I have no idea what to do with it
Affected view:
class DropDownList(ListView):
context_object_name = 'drop_down'
template_name="browse.html"
model=ServiceList
asd = ''
def post(self, request):
name = request.POST.get('selected')
obj = ServiceList.objects.get(sourceFile=name)
print(obj)
print(request.POST) #some prints for debugging
context = {'asd': obj}
return render(request, self.template_name, context)
Models:
class ServiceList(models.Model):
version = models.IntegerField()
location = models.CharField(max_length=500)
services = models.ManyToManyField(Service)
drms = models.ManyToManyField(Drm)
sourceFile = models.CharField(max_length=500)
class Service(models.Model):
uid = models.CharField(max_length=255, unique=True)
locations = models.ManyToManyField(Location)
names = models.ManyToManyField(Name)
category = models.CharField(max_length=500)
drm = models.CharField(max_length=500)
description = models.CharField(max_length=500)
html fragment:
<p>
<br/>
<form action="/browse/" id="tableform" method="POST">{% csrf_token %}
<select class="form-select" aria-label="Default select example" form="tableform" name="selected">
<option selected>-----------------</option>
<option >One</option>
<option >Two</option>
<option >Three</option>
{% for obj in drop_down %}
<option name={{obj.sourceFile}}>{{obj.sourceFile}}</option>
{% endfor %}
</select>
</form>
<button id="add-list" type="submit" class="btn btn-success pull-left" class="button1" form="tableform">+</button>
</p>
<table class="table table-bordered table-dark">
<tr>
<th>UID</th>
<th>Category</th>
</tr>
{% for obj in drop_down %}
adasdasd
{% if obj.sourceFile == asd %}
<br/>fghfghfgh
{% for service in obj.services.all %}
<tr>
<td>{{service.uid}}</td>
<td>{{service.category}}</td>
</tr>
{% endfor %}
{% endif %}
{% endfor %}
</table>
There are a few things wrong in your code, I'm going to list them, so it is easier to understand.
1 - context_object_name = 'drop_down' is not being used, 'drop_down' is not being declared in your class.
2 - You don't need to assign 'asd' as an empty variable, since you are only using as a namespace for the obj variable in your context set.
3 - Your return function is wrong, you should only be returning the context there, since you are using a class based view.
4 - You are using a ListView to get only one object
I don't know if this is going to work(I don't have a lot of experience in class based views) but try to make your code look like this:
class DropDownList(DetailView):
template_name="browse.html"
model=ServiceList
def post(self):
name = request.POST.get('selected')
obj = ServiceList.objects.get(sourceFile=name)
context = {'asd': obj}
return context
you can delete the context_object_name line as I mentioned, so if you want to render your ServiceList object version field in and HTML page for example, you should use {{ asd.version }}
Also, try to look for function based views, they are better to use, im assuming that you are using this view to get an object from your database, if you were using a function based view here, your code would be much simpler, it would look like this
def get_object(request, object_id):
template = 'browse.html'
obj = ServiceList.objects.get(pk=object_id)
context = { 'youcandecideanamehere': obj }
return render(request, template, context)
but you would need to pass the object_id parameter in your url(I'm going to create some here, since you didn't give yours)
urlpatterns = [
...
path('get_object/<object_id>/', get_objects, name='get_objects')
...
]
And the a tag for that page, should be like this:
Text
Hope I've helped, but if you still have some problems, feel free to contact me.
Suppose I have a Item model, where Item objects can either be public (accessible to all users) or private (accessible only to authenticated users):
class Item(models.Model):
title = models.CharField(max_length=100)
is_public = models.BoleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
#...
secret_key = ...
class Meta:
# I need to keep items in order:
ordering = ('-created_at',)
What I need is to list all items using a generic.ListView - keeping the order - but hide the secret_key of those items with is_public=False for anonymous users.
So in the template, I hide the secret_key if the user is not authenticated, like:
{% if request.user.is_authenticated %}
<p>{{ item.title }} - {{ item.secret_key }}</p>
{% else %}
<p>{{ item.title }} - This item is private. Sign up to see the secret_key!</p>
{% endif %}
and the ListView is like:
class ItemListView(ListView):
model = Item
paginate_by = 10
I'm aware that I can send two separate querysets for non logged-in users to the template, one for public items and the other for private ones; but I'm not sure how can I keep the order ('-created_at') in this approach.
The question is:
Is it safe to send all the secret_keys to the template and hide them for non logged-in users there?
(if it is safe, then) Is there a more efficient way of doing this?
I tried overriding the get_queryset method of my ItemListView and move the if condition from template to there (I think this would increase the performance, right?). I handled the situation where the users is authenticated (simply return all the objects); but for non logged-in users, I thought about somehow joining two separate querysets, one holding the public items and the other holding only the title and created_at of private items; but I didn't find to keep the order in this approach:
class ItemListView(ListView):
model = Item
paginate_by = 10
def get_queryset(self):
if self.request.user.is_authenticated:
return Item.objects.all()
else:
# ???
This was only a minimal-reproducible-example; Actually in the project, I have multiple access_levels; Each user has an access_level, based on their plan (e.g. basic, normal, pro, etc.) and each Item has an access_level; And an I'm dealing with about +100K objects, fetched from different databases (postgresql - some cached on redis) so the performance really matters here. Also the system is up-and-running now; so I prefer less fundamental solutions.
Thanks for your time. Your help is greatly appreciated.
Is it safe to send all the secret_keys to the template and hide them for non logged-in users there?
Your template is rendered server-side, and the client only get the rendered markup, so yes, it is totally safe. Well, unless someone in your team messes with the template code of course ;-)
(if it is safe, then) Is there a more efficient way of doing this?
Just filter the queryset in your view - you don't need two distinct querysets, and filtering the queryset will not change it's ordering.
def get_queryset(self):
qs = super(ItemListView, self).get_queryset()
if not self.request.user.is_authenticated:
qs = qs.filter(is_private=False)
return qs
and in your template:
{# avoids doing the same constant lookup within the loop #}
{% with is_auth=request.user.is_authenticated %}
{# I assume the queryset is named "objects" in the context ?#}
{% for item in objects %}
<p>{{ item.title }}{% if is_auth %} - {{ item.secret_key }}{% endif %}</p>
{% endfor %}
{% endwith %}
EDIT: bdoubleu rightly mentions in his answer that his solution makes testing easier. If you only need fields from your model (no method call), you can also use QuerySet.values() instead:
def get_queryset(self):
qs = super(ItemListView, self).get_queryset()
fields = ["title", "created_at"]
if self.request.user.is_authenticated:
fields.append("secret_key")
else:
qs = qs.filter(is_private=False)
return qs.values(*fields)
This will also make your code a bit more efficient since it doesn't have to build full model instances.
Another option is to annotate the queryset to add an extra attribute for display_secret_key which is going to be more efficient than checking the user access level for each item in the queryset while templating.
from django.db.models import F, Value as V
class ItemListView(ListView):
queryset = Item.objects.all()
paginate_by = 10
def get_queryset(self):
annotations = {
'display_secret_key': V('')
}
if self.request.user.access_level == 'PRO':
annotations['display_secret_key'] = F('secret_key')
return (
super().get_queryset()
.annotate(**annotations)
)
Then in your template:
<p>{{ item.title }} - {{ item.display_secret_key }}</p>
You could use 2 Templates, one for the authenticated user one for the unauthenticated. (just overwrite the get_template_names() for authentication check and add something like _sectempl.html to the found name and add the appropriate copy of the template with the secret data)
But I would say with bruno desthuilliers that if you switched off the debug mode there could be no constellation where unauthenticated users see content within
{% with authenticated=request.user.is_authenticated %}
{% if authenticated %}
do secret stuff
{% endif %}
{% endwith %}
or
{% if request.user.is_authenticated %}
hide secret stuff for all the others
{% endif %}
If you got a complex user-grouping-combination outside the standard django user right management (where you could ask for user-permissions in templates) then I would write the user_status (your "plan" or accesslevel) into the user-session (while authentication) and check for this user_status in the output-function of the attribute of the object.
Sketch:
Use in template:
{% for i in object_list %}
{{ i.name}}, {{ i.print_secret }}
{% endfor %}
In the model you create a "print_secret"-method witch returns the secret according to the previous recorded user_status in the session-data.
I'm trying to pass an object to my HTML template consisting of the parent object and all child objects that relate to it. For instance:
Model Chamber:
class Chamber(models.Model):
chamber_name = models.CharField(max_length=100)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
Has ChamberProperty:
class ChamberProperty(models.Model):
chamber = models.ForeignKey(Chamber, on_delete=models.CASCADE)
property_name = models.CharField(max_length=50)
property_value = models.CharField(max_length=100)
user_defined = models.BooleanField(default=True)
They are two separate models because the customer can add as many properties as they want to their chamber.
In my views.py
class ChambersView(generic.DetailView):
template_name = 'pages/chambers.html'
def get(self, request):
user = User.objects.get(username=request.user)
customer = Customer.objects.get(user=user)
chambers_list = list(Chamber.objects.filter(customer=customer))
try:
chamber_properties = list(ChamberProperty.objects.filter(chamber__in=chambers_list).order_by('id'))
except:
chamber_properties = "No properties"
form = ChambersFilterForm(request=request)
return render(request, self.template_name, {'filter_form':form, 'chambers_list': chambers_list, 'chamber_properties': chamber_properties})
Now, that does get me a list of all chambers, and a list of all chamber properties. Except they're not linked to each other. I'm not sure how to build a list of related objects. I read about backwards relationships just now, but I don't seem to grasp how to use them.
I tried the following:
chambers_and_props = Chamber.chamberproperty_set.all()
And I get the following error:
AttributeError: 'ReverseManyToOneDescriptor' object has no attribute 'all'
So I'm not quite sure how to use it. The threads I saw mentioned that a relationship in Django automatically add a reverse on ForeignKeys and the usage should be parent.child_set.all() with child in lower caps.
I get a ReverserManyToOneDescriptor object, but not sure how to turn that into a usable list I can pass on to my HTML template.
Any ideas as to what I'm doing wrong?
Your query does not work because you have not specified which Chamber you want to get the backwards relation for.
However, this is not the right approach. Presumably you want the ChamberProperty so you can list them against each Chamber in the template. So, you should follow the relationship there in the template - there's no need to query ChamberProperty separately in the view at all.
{% for chamber in chamber_list %}
{{ chamber.chamber_name }}
{% for property in chamber.chamberproperty_set.all %}
{{ property.property_name }} : {{ property.property_value }}
{% endfor %}
{% endfor %}
You are getting the error because you are trying Chamber.chamberproperty_set on the model Chamber. It will work on individual chamber instances:
You can do this in the view:
for chamber in chambers_list
properties = chamber.chamberproperty_set.all()
Or in the template:
{% for chamber in chambers_list %}
{{ chamber }}
{% for property in chamber.chamberproperty_set.all %}
{{ property }}
{% endfor %}
Then, in your view, you can use prefetch_related to reduce the number of SQL queries:
chambers_list = Chamber.objects.filter(customer=customer).prefetch_related('chamberproperty_set')
I am trying to optimize a Django project (vers. 1.8.6) in which each page shows 100 companies and their data at once. I noticed that an unnecessary amount of SQL queries (especially with contact.get_order_count) are performed within the index.html snippet below:
index.html:
{% for company in company_list %}
<tr>
<td>{{ company.name }}</td>
<td>{{ company.get_order_count }}</td>
<td>{{ company.get_order_sum|floatformat:2 }}</td>
<td><input type="checkbox" name="select{{company.pk}}" id=""></td>
</tr>
{% for contact in company.contacts.all %}
<tr>
<td> </td>
<td>{{ contact.first_name }} {{ contact.last_name }}</td>
<td>Orders: {{ contact.get_order_count }}</td>
<td></td>
</tr>
{% endfor %}
{% endfor %}
The problem seems to lie in constant SQL queries to other tables using foreign keys. I looked up how to solve this and found out that prefetch_related() seems to be the solution. However, I keep getting a TemplateSyntaxError about being unable the parse the prefetch, no matter what parameter I use. What is the proper prefetch syntax, or is there any other way to optimize this that I missed?
I've included relevant snippets of model.py below in case it's relevant. I got prefetch_related to work in the defined methods, but it doesn't change the performance or query amount.
model.py:
class Company(models.Model):
name = models.CharField(max_length=150)
def get_order_count(self):
return self.orders.count()
def get_order_sum(self):
return self.orders.aggregate(Sum('total'))['total__sum']
class Contact(models.Model):
company = models.ForeignKey(
Company, related_name="contacts", on_delete=models.PROTECT)
first_name = models.CharField(max_length=150)
last_name = models.CharField(max_length=150, blank=True)
def get_order_count(self):
return self.orders.count()
class Order(models.Model):
company = models.ForeignKey(Company, related_name="orders")
contact = models.ForeignKey(Contact, related_name="orders")
total = models.DecimalField(max_digits=18, decimal_places=9)
def __str__(self):
return "%s" % self.order_number
EDIT:
The view is a ListView and defines the company_list as model = Company. I altered the view based on given suggestions:
class IndexView(ListView):
template_name = "mailer/index.html"
model = Company
contacts = Contact.objects.annotate(order_count=Count('orders'))
contact_list = Company.objects.all().prefetch_related(Prefetch('contacts', queryset=contacts))
paginate_by = 100
Calling the get_order_count and get_order_sum methods causes one query every time the method is called. You can avoid this by annotating the queryset.
from django.db.models import Count, Sum
contacts = Contact.objects.annotate(order_count=Count('orders'), order_sum=Sum('orders'))
You then need to use a Prefetch object to tell Django to use your annotated queryset.
contact_list = Company.objects.all().prefetch_related(Prefetch("contacts", queryset=contacts)
Note that you need to add the prefetch_related to your queryset in the view, it is not possible to call it in the template.
Since you are using ListView, you should be overriding the get_queryset method, and calling prefetch_related() there:
class IndexView(ListView):
template_name = "mailer/index.html"
model = Company
paginate_by = 100
def get_queryset(self):
# Annotate the contacts with the order counts and sums
contacts = Contact.objects.annotate(order_count=Count('orders')
queryset = super(IndexView, self).get_queryset()
# Annotate the companies with order_count and order_sum
queryset = queryset.annotate(order_count=Count('orders'), order_sum=Sum('orders'))
# Prefetch the related contacts. Use the annotated queryset from before
queryset = queryset.prefetch_related(Prefetch('contacts', queryset=contacts))
return queryset
Then in your template, you should use {{ contact.order_count }} instead of {{ contact.get_order_count }}, and {{ company.order_count }} instead of {{ company.get_order_count }}.
Try this in views.py
company_list = Company.objects.all().prefetch_related("order", "contacts")
I want to make a page with a list of users and checkboxes that signal if a user is selected, which will apply some action to selected users.
I created a form class which looks like this:
#in forms.py
class UserSelectionForm(forms.Form):
"""form for selecting users"""
def __init__(self, userlist, *args, **kwargs):
self.custom_fields = userlist
super(forms.Form, self).__init__(*args, **kwargs)
for f in userlist:
self.fields[str(f.id)] = forms.BooleanField(initial=False)
def get_selected(self):
"""returns selected users"""
return filter(lambda u: self.fields[str(u.id)], self.custom_fields)
In my template I have users listed in a table and I want the last column of this table to be those checkboxes. I need to render fields one by one depending on their name.
I tried creating a template tag that would return the html code of the needed form element:
#in templatetags/user_list_tags.py
from django import template
register = template.Library()
#this is django template tag for user selection form
#register.filter
def user_select_field(form, userid):
"""
returns UserSelectionForm field for a user with userid
"""
key = std(userid)
if key not in form.fields.keys():
print 'Key %s not found in dict' % key
return None
return form.fields[key].widget.render(form, key)
Finally, here's the template code:
<form action="" method="post">
{% csrf_token %}
<table class="listtable">
<tr>
<th>Username</th>
<th>Select</th>
</tr>
{% for u in userlist %}
<tr>
<td>{{u.username}}</td>
<td>{{select_form|user_select_field:u.id}}</td>
</tr>
{% endfor %}
</table>
<p><input type="submit" value="make actions" /></p>
However, this does not bind those widgets to the form and thus, after submitting the form, validation fails. The error message says that all the custom fields are required.
So here are my questions:
What is the right way to render separate form fields?
What is the right way of creating such a form with checkboxes? (I mean maybe my method is stupid and there is a much easier way of achieving what I want.
You're making the template far too complicated. Add a label to each field when you create it in the form's __init__ method.
for f in userlist:
self.fields[str(f.id)] = forms.BooleanField(label=f.username, initial=False)
Then just loop over the fields in the form and don't worry about the userlist anymore.
{% for field in form %}
<tr>
<td>{{ field.label_tag }}</td>
<td>{{ field }}</td>
</tr>
{% endfor %}
Ok So I think I have found a way to correctly render separate form fields. I found it watching django sources. Django.forms.forms.BaseForm class has _html_output method which creates an instance of Django.forms.forms.BoundField and then adds unicode(boundField) to the html output. I did the exact same thing and it worked perfectly:
#in templatetags/user_list_tags.py
from django import template
from django import forms
register = template.Library()
#this is djangp template tag for user selection form
#register.filter
def user_select_field(form, userid):
"""
returns UserSelectionForm field for a user with userid
"""
key = str(userid)
if key not in form.fields.keys():
print 'Key %s not found in dict' % key
return None
#here i use BoundField:
boundField = forms.forms.BoundField(form, form.fields[key], key)
return unicode(boundField)
That generated the same html as {{form.as_p}}, so the POST request will look exactly the same and form will be processed correctly.
I also fixed some mistakes in my form class:
#in UserSelectionForm definition:
...
#__init__
for f in userlist:
self.fields[str(f.id)] = forms.BooleanField(initial=False, required=False)
#get_selected
return filter(lambda u: self.cleaned_data[str(u.id)],
self.custom_fields)
That now seems to work as I planned, without any javascript.