Django template: how to randomize order when populating page with objects? - python

I have a Survey and a Choice model, and each survey has a number of choices associated with it. When I'm rendering the actual HTML survey page with all the choices, I use the following Django templating code:
{% for choice in survey.choice_set.all %}
<li class="ui-state-default" choice_id={{ choice.id }}>{{ choice.choice_text }}</li>
{% endfor %}
However, instead of the choices appearing in the same order every time, I'd like them to populate in a random order to reduce any potential bias effect (e.g. someone may be more likely to vote for options that appear first on the list).
If there's a way to do this within the template itself, that'd be great, but it seems more likely that I'd need to do something in the backend in views.py. I've already tried this, to no effect:
class DetailView(generic.DetailView):
model = Survey
...
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
...
survey = get_object_or_404(Survey, survey_link__iexact=survey_link)
...
if randomize_choice_order:
survey.choice_set.order_by('?')
...
return context
Any idea how I can accomplish this? Maybe I need to instead develop a JS function to randomize the objects after they've already been placed?

You can create a custom template tag to shuffle your results.
# app/templatetags/shuffle.py
import random
from django import template
register = template.Library()
#register.filter
def shuffle(arg):
aux = list(arg)[:]
random.shuffle(aux)
return aux
Then in your template
{% load shuffle %}
{% for choice in survey.choice_set.all|shuffle %}

With Django 1.7+ you can use the Prefetch object:
survey = get_object_or_404(
Survey.objects.prefetch_related(
Prefetch('choice', queryset=Choice.objects.order_by('?'),
to_attr='random_choices')
),
survey_link__iexact=survey_link
)
You can then access the random set with survey.random_choices. The original choice set is still available with survey.choice_set.all().

Related

Wagtail - how to (correctly) get other pages/models

Using Wagtail which is the more appropriate way of getting the records of other pages/models.
# models.py
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context['children'] = ArticleDetailPage.objects.live().public()[:3]
return context
# template.html
{% for item in children %}
<li>{{ item }}</li>
{% endfor %}
Or,
# models.py
def articles(self):
articles = ArticleDetailPage.objects.live().public()[:3]
return articles
# template.html
{% for item in page.articles %}
<li>{{ item }}</li>
{% endfor %}
Both of these methods work and I see the former often used in the docs and either one used elsewhere. Is there a difference at all to which one is used in terms of performance or "correctness" or is it simply personal preference?
AFAIW, it's just personal preference. The first one is more explicit, the second one keeps your context more clean. The only difference I can think of is that in the second option ArticleDetailPage.objects.live().public()[:3] is only called when you actually include {{page.articles}} in your template, which could lead to a tiny boost in performance if the query is quite complex.
In general I tend to wrap these kind of variables in a property when either:
I know there is an explicit relationship there. So in this case, when they're subpages of the page rendering the template. A variable like shopping_cart or breadcrumb would be less likely to be coupled to the page itself.
or
The variable is used in other parts of my Django/Wagtail code apart from the template. It's very much possible that I want to access the articles of a page from somewhere else.
Otherwise, I just pass it directly in the context.

how to use django model object in django templates?

I am not able to use the Django model object in Django templates. I want to iterate using the model user in the template and then play with the ActivityPeriod(model) of that user. Please check my code for the clarity:
Here is my code:
views.py
from .models import User,ActivityPeriod
def call_response(request):
user = User.objects.all()
return render(request, "Test/list.html", {"users":user ,"activityperiod":ActivityPeriod})
Test/list.html
{% for user in users %}
'real_name': {{ user.real_name}}},
'activity_periods': {% with activity=activityperiod.objects.get(id =user) %}
{{ activity.start_time }}
{% endwith %}
{% endfor %}
But i am getting an error:
Could not parse the remainder: '(id' from 'activityperiod.objects.get(id'
What is the correct way? Can anyone please share it with me.
Django template don't understand the filter action of Model. This part shoud be in view.
activity=activityperiod.objects.get(id =user)
You should prepare your data and manipulate them before sending to template (a dictionary may help you). And remember that result of action "User.objects.all()" is a list.
views.py
def call_response(request):
user = User.objects.filter(user=request.user)
activityperiod = activityperiod.objects.get(user=user)
context={'user':user,'activityperiod':activityperiod}
return render(request, "Test/list.html",context})
Test/list.html
'real_name': {{ user.real_name}}
'activity_periods':{{ activityperiod.start_time }}
Your question suggests that you think you can a function in the templates like a normal function (ie activityperiod.objects.get(...)).
You can't, the templating system is not made like this (for security reasons amongst others).
You should do something like, in your models:
def call_response(request):
# ! first() not "all()" (if > 1 user, you'll have problem)!
user = User.objects.first()
activityperiod = activityperiod.objects.get(user=user)
return render(request, "Test/list.html",
{"users":user ,"activityperiod":activityperiod})

Django generic model ListViews & forms

Following from my question Django sharing one model between two others which explored the best way to generically manage two similar models and their dependencies. I've now come to the next part - the views.
After a bit of tweaking around I've come to the following generic listview to represent the objects.
Is this coherent? Should the urls manage the models like this?
I believe if I refine their logic I can just subclass this itemlist and add additional logic in the future so it should be future-proofed.
A class that
urls.py excerpt
urlpatterns = [
url(r'^item/(?P<model>\w+)/$',views.ItemList.as_view(),name='generic'),
url(r'^item/(?P<model>\w+)/(?P<pk>\d+)$',views.ItemList.as_view(),name='generic'),
]
views.py excerpt
class ItemList(ListView):
context_object_name = 'itemlist'
template_name = 'food/item.html'
model = None
def get_queryset(self):
model = apps.get_model('food',self.kwargs.get('model'))
if self.kwargs.get('pk'):
print ('pk provided')
return model.objects.filter(pk=self.kwargs.get('pk'))
else:
print ('all')
return model.objects.all()
food/item.html excerpt (using registered filter to get class name for object)
{% block content %}
{% for item in itemlist %}
...
{% for review in item.reviews.all %}
{% endfor %}
...
<span class="glyphicon glyphicon-plus"> Add Review </span>
{% endblock %}
Also - can I carry this attitude into my forms (again assuming that when I add a review it'll generically link back to whatever object it came from?) or is that where I should start breaking them up?

Query not returning values

So I'd like to think what I'm doing is fairly common. I'm attempting to display a result set using Django. I have looked online for a while and here is what I've come up with:
models.py{
from django.db import models
from django.conf import settings
class SalesRep(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
class Sales(models.Model):
seller = models.ForeignKey(SalesRep)
sold_on = models.DateField("Sold On")
}
sales_view.py{
from salesteam.models import SalesRep, Sales
from django.views.generic import ListView
class SellerHome(List View):
model = SalesRep
template_name = 'production/sales.html'
#property
def rep_sales(self):
return Sales.objects.filter(SalesRep=self)
}
sales.html{
<div id="total-sales">
{% for sale in SellerHome.rep_sales%}
<li>{{ sale.id }}</li>
{% empty %}
<li>You don't currently have any sales</li>
{% endfor %}
</div>
}
For the sake of completeness I even tried writing just the property out as Sales.objects.all() but still no luck. Thanks for taking a look as I'm fairly new to HTML, Django, and Python. Any help on what I'm doing wrong would be much appreciated. Let me know if you have any questions :)
I'm not sure what tutorial you've followed, but there is a lot of errors in your code... I'm assuming you would like to display all Sales for the logged in user?
In this scenario, I would change the model slightly to add a related_name to Sales foreign key to SalesRep:
class SalesRep(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
class Sales(models.Model):
seller = models.ForeignKey(SalesRep, related_name='sales')
sold_on = models.DateField('Sold On')
Documentation: https://docs.djangoproject.com/en/1.7/ref/models/fields/#django.db.models.ForeignKey.related_name
And I would change the views to use DetailView:
from django.views.generic import DetailView
class SellerDetailView(DetailView):
model = SalesRep
template_name = 'production/sales.html'
context_object_name = 'seller'
def get_object(self):
return self.request.user
Docs on DetailView — https://docs.djangoproject.com/en/1.7/ref/class-based-views/generic-display/#detailview
<div id="total-sales">
<ul>
{% for sale in seller.sales.all %}
<li>{{ sale.id }}</li>
{% empty %}
<li>You don't currently have any sales</li>
{% endfor %}
</ul>
</div>
First, I'd recommend avoiding class based views, at least as you are learning Django, since they are not really necessary and needlessly complicated for what you are doing.
Anyway, to answer your question using class-based views, your problem is in your template and your view. You'll want to put your rep_sales logic in a get_queryset, set the model to be Sales, not SalesRep, and then reference object_list in the template. More info.
However, I'd instead recommend writing a normal, function-based Django view. Something like this might get you started:
def view_all_sales(request):
my_sales_rep = SalesRep.objects.get(request.user)
all_my_sales = Sales.objects.filter(seller=my_sales_rep)
return render(request, "production/sales.html", {
"sales": all_my_sales
})

Compare attributes of a Django Queryset in a template using the `in` Operator

I'm trying to use the in operator to determine if a template variable on the current page is also a foreign key in another model.
The model is like so:
class WishlistItem(models.Model):
user = models.ForeignKey(User, related_name='wishlist_items')
issue = models.ForeignKey(Issue)
On the "Issue" page template, I'm trying to do this:
{% if issue in user.wishlist_items %}
{% else %}
{# A button to add the issue to the user's wishlist #}
{% endif %}
What I need to do is to get at the issue attribute of the wishlist_items QuerySet for the currently logged-in user and compare it to the issue object of the current page. I'm at a loss for how to do this. Do I need to write a templatetag to do this?
I ended up solving this by writing a template filter:
#register.filter
def in_list(obj, arg):
"Is the issue in the list?"
return obj in (item.issue for item in arg)
Then I could do something like this in a template:
{% if issue|in_list:user.wishlist_items.all %}
I got the idea from the answer on this Stack Overflow question.
It seems that there's a relationship between User and wishlist issues that you ought to express as a ManyToManyField on the User. If there is extra metadata about the relationship that you need to record in a WishlistItem, then use through=WishlistItem.
If the User model is from the auth app, you'll need to extend the User model to achieve this (see this blog post for some help). For example:
class MyUser(User):
wishlist_issues = models.ManyToManyField(Issue, through=WishlistItem)
# ...
Then, in your template use:
{% if issue not in user.wishlist_issues %}
{# A button to add the issue to the user's wishlist #}
{% endif %}
wishlist_items is not a queryset - it's a manager. wishlist_items.all IS a queryset, but obviously it is a queryset of WishlistItem objects, not Issue objects. What I would do is handle this logic in the view... something like:
user_has_issue = issue in (wl.issue for wl in request.user.wishlist_items.all())
If I understood correctly, instead of writing a templatetag for it you can pass that as a parameter of the view.
def myView(request):
return render_to_response('template.html',
{'query': queryToCheckIfIssueInWishList},
RequestContext(request))
I think what you are refering to should go into the view, more than the template.
Of course, you could create some templatetag, however Django's approach to templates is to maintain them as dumb as possible, and move all logic to the view.
On the other hand, moving the logic in the view, and then passing the additional information to the template should be quite straightforward, and I would definitely go for that solution.

Categories