Sorting queryset results in a template - python

I have the following model:
class TestCase(models.Model):
tc_id = models.CharField(max_length=20)
tc_title = models.CharField(max_length=500)
class TestSteps(models.Model):
ts_case = models.ForeignKey(TestCase, on_delete=models.CASCADE)
ts_seq_no = models.IntegerField(default=1)
ts_instruction = models.CharField(max_length=200)
I want to display a test case together with its associated test steps in the template. For this I have written two views, one is not so nice but works:
def tc_steps(request, pk):
case = TestCase.objects.filter(id=pk)
steps = TestSteps.objects.filter(ts_case_id=pk).order_by('ts_seq_no')
context = {'case': case, 'steps': steps}
return render(request, 'testman/tc_steps.html', context)
Not very nice because I have to retrieve two querysets. Better to have this one:
def tc_steps(request, pk):
case = TestCase.objects.filter(id=pk)
return render(request, 'testman/tc_steps.html', {'case': case})
because this contains all the information I need in the template. Now the problem:
In the template for the second view I use the following tag to display the test steps:
{% for step in case.first.teststeps_set.all %}
Which works but the steps aren't in the right order. In the template for the first view I just use:
{% for step in steps %}
And get the correct order (sorted by ts_seq_no) because I did the sorting in the view already. I tried to use a filter but couldn't find one that does what I want. My question is, is there any way to do an order_by in the template tag?

You can use dictsort like this(use dictsortreversed for reversed order):
{% for step in case.first.teststeps_set.all|dictsort:"ts_seq_no" %}

I would add a method to the TestCase model to return its related steps in the required order.
class TestCase(models.Model):
...
def ordered_steps(self):
return self.teststeps_set.order_by('ts_seq_no')
Now in the template you can do {% for step in case.first.ordered_steps %}.

Related

Injecting custom data into Django Queryset before passing to template

What is the best way to append or inject some extra data into a Django QuerySet?
Imagine a situation where I am displaying a list of Books, and I want to show the result of a special calculation on each one:
models.py
class Book(models.Model):
name = models.CharField(max_length=64)
book_list.html
{% for book in objects %}
{{ book.name }} - {{ book.special_result }}
{% endfor %}
views.py
class BookListView(ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
books = self.object_list
for book in books:
book.special_result = do_special_calculation(foo=bar)
context['books'] = books
return context
Imagine that do_special_calculation() method cannot be calculated in the template or as a model parameter, because it needs to have a variable foo passed in.
This code does not achieve the desired result of making each book's special_result value accessible from the template, because the book variable is overwritten with each iteration of the for loop. Every solution I've come up involves basically building out a new dictionary in parallel with the QuerySet, passing that into the template, and looping through them both in the template simultaneously, causing very ugly code.
I also don't want to save the result of do_special_calculations() back to the database for a host of reasons (efficiency, potential stale data, can't easily save an object).
What would be the best approach to make each entry's special calculation available in the template?
I finally solved this by making an empty list and using setattr() on each entry. Here is a fixed code example:
class BookListView(ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
books = self.object_list
book_list = []
for book in books:
special_result = do_special_calculation(foo=bar)
setattr(book, "special_result", special_result)
book_list.append(book)
context['books'] = book_list
return context

Chaining Django models: how to format results on the template according to the specific model

I have a view that allows me to work with two different models at once, thanks to itertools chain. I'm rendering the instances of the two chained models inside a table in my template, and I'd need the rows of the table to be formatted differently in case the instances are from one model as opposed to the other.
So basically: I'm chaining two models and displaying their instances in a table, and all the rows of the table that contain instances from model A should be formatted with a yellow background and all the rows containing instances from model B should have a blue background instead.
This is the view:
class BaseView(generic.ListView):
template_name = 'base/base_list.html'
context_object_name = 'base_list'
def get_queryset(self):
queryset = Document.objects.order_by('due_date')
return queryset
def get_context_data(self, **kwargs):
context = super(BaseView, self).get_context_data(**kwargs)
context['object_list'] = sorted(
itertools.chain(Program.objects.all(), Document.objects.all()),
key=attrgetter('validity_date'),
reverse=True)
return context
In logic, what I'd need in the template would be something like this:
if
object in object_list ***belongs*** to Program.objects.all()
(etc)
else
(etc)
The question is: how should I express that belongs?
I've also looked into template tags but could not find the right way to go.
Thank you in advance.
As I mentioned in the comments, you should look for a way of identifying the model itself rather than checking if it is in a list. There is a built-in way of accessing the model name, but unfortunately that is inside the _meta attribute and you're not allowed to use attributes that start with underscores in a template.
So instead I would recommend simply adding one to your class definitions:
class Program(models.Model):
model_name = 'Program'
...
Now you can just do:
{% if object.model_name == 'Program' %}
...
{% else %}
...
{% endif %}

Using one model to filter another model in Django

I'm trying to access the information in my gadb_action model based on the action_ids in my gadb_vote model. I'm initially only getting the information for a particular legislator and then attempting to get the bills associated with the actions that legislator has voted on.
Right now, my action_list is only storing action_ids, but not the related information from the gadb_action model that I want to use in my template.
What is the best way to store that information outside of the for loop to be accessed by the template? Is there a way to write to an empty QuerySet?
Thanks in advance for any and all help!
view
def each_member(request,legislator_id):
each_member = get_object_or_404(gadb_legislator, legislator_id=legislator_id)
each_vote = gadb_vote.objects.filter(legislator_id=legislator_id)
action_list = []
for i in each_vote:
action = gadb_action.objects.filter(action_id=i.action_id)
action_list.append(action)
context = {
'each_member': each_member,
'each_vote': each_vote,
'action_list': action_list
}
return render(request, "eachmember.html", context)
models
class gadb_action(models.Model):
action_id = models.IntegerField(unique=False, max_length=4, primary_key=True)
bill_id = models.IntegerField(unique=False, max_length=12)
class gadb_vote(models.Model):
vote_id = models.IntegerField(unique=False, max_length=11,primary_key=True)
legislator_id = models.IntegerField(unique=False, max_length=11)
action_id = models.IntegerField(unique=False, max_length=11)
template
{% for i in action_list %}
{{i.bill_id}}
{{i.action_id}}
{% endfor %}
Your models are broken.
For a start, although it's not directly related to the question, you need to define your primary keys as AutoFields so that they are autoincremented every time a new entity is added. Otherwise you'll get all sorts of errors when you save a new row. (Even better, don't define the PK at all, and let Django add it automatically.)
Secondly, as lalo says, you should have ForeignKeys from Action to Bill, and from Vote to Action and Vote to Legislator. That way you can get the relevant information with a single query, and follow the foreign keys as required in your template.
(Also, Django already includes the app name in the underlying table name: no need to prefix everything with 'gadb'.)
class Action(models.Model):
bill = models.ForeignKey(Bill)
class Vote(models.Model):
legislator = models.ForeignKey(Legislator)
action = models.ForeignKey(Action)
View:
def each_member(request,legislator_id):
actions = Action.objects.filter(vote__legislator_id=legislator_id)
return render(request, "eachmember.html", {'action_list': actions})
Template:
{% for action in actions %}
{{ action.bill.name }}
{{ action.someotherfield }}
{% endfor %}

Queryset object has no attribute 'Name'

I am working on my first django project and i am having problems displayin 'categories' from my database onto a webpage as a list. I am getting the error "object has no attribute 'Name'. My code so far is:
Model:
class Category(models.model):
name = models.Charfield(max_length=128)
def __unicode__(self):
return self.Name + ": " +str(self.id)
Views:
from django.shortcuts import render_to_response, redirect
from forms.models import Form, Group, Flow, Gate, Field, Event, Category
from django.core.context_processors import csrf
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
def homepage (request):
CatName = Category.objects.order_by('id')
output = {
'category_name': CatName.Name,
}
return render_to_response('forms/formsummary.html', output)
HTML:
<div>{{ category_name }}</div>
Can anybody point me in the right direction?
In Django, when you use the ORM to query for objects, there are two possibilities (excluding each case returning nothing):
Query returns just one objects: if so, you queried using the get() method of the manager.
Query returns a collection: if so, you queried by using an all(), filter() or any method like those.
In this case, your query returned a collection of Category objects, you can do a couple of things about this, you can either generate a list with only the names by using a list comprehension:
cnames = [c.name for c in Category.objects.all()]
Or you can iterate the list using a for loop and do whatever you need to do with each object.
Django already orders your data by the id field, so, I guess there is no need to specify an ordering in this case.
Later, when your view is returning, you can deliver the list to your template and iterate it to extract what you need, for example.
In your view:
def get_categories(request):
categories = Category.objects.all()
context = {'categories': categories}
return render_to_response('template.html', RequestContext(request, context))
Then, in your template:
{% for c in categories %}
<p>{{c.name}}</p>
{% endfor %}
Here's some useful documentation
Django Templates
Django Querysets
Hope this helps.
It seems like case sensitive,
def__unicode__(self):
return self.Name + ": " +str(self.id)
^
name
CatName is a collection of Category instances. The CatName object does not have a name property because it is not a Category object. It contains Category objects.
you can iterate through your collection and display each categories name:
for category in CatName:
print category.name
It is good to at least read through QuerySet documentation even if you don't fully grasp it yet.
if you want just the most recent category you could do something like:
def homepage (request):
most_recent_category = Category.objects.order_by('-id')[0]
output = {
'category_name': most_recent_category.name
}
return render_to_response('forms/formsummary.html', output)

Looping through two objects in a Django template

I have an app objects and image objects which are linked to one another (the apps have images).
def index(request):
latest_apps_list = App.objects.all().exclude(approved=False).order_by('name')[:20]
app_images = Image.objects.filter(app__in=latest_apps_list).order_by('app__name')[:20]
t = loader.get_template('apps/index.html')
c = Context({
'latest_apps_list': latest_apps_list,
'app_images': app_images
})
return HttpResponse(t.render(c))
Now I want to loop through those images in my template. How would I do that with both variables? I tried using zip(), but this returned mysql errors as it calls for unsupported db queries. Is there another way?
Currently I have:
{% for app in latest_apps_list %}
...{{ app.name }}
{% endfor %}
This works. Of course, it doesn't return the images urls. (I'm using sorl-thumbnail btw.)
UPDATE Perhaps I'm just going about doing this the wrong way. Here's how I have my model:
class App(models.Model):
name = models.CharField(max_length=200)
# ...
class Image(models.Model):
app = models.ForeignKey(App)
image = models.ImageField(upload_to = "apps")
And my view is in the original part of the post above. It seems like I should somehow be making the app's properties and the image properties all one thing, without the need to zip in the view. Is this possible?
UPDATE 2 I solved this by greatly simplifying how the model is created. Here's what I did in case anyone else is trying to do this.
apps/admin.py: the image object is included as an ordinary field.
class AppAdmin(admin.ModelAdmin):
fieldsets = [
('Basic', {'fields':['name','desc','price','approved','image']}),
('Author', {'fields':['docs_url', 'preview_url']}),
]
list_display = ('name', 'desc', 'price', 'approved')
admin.site.register(App, AppAdmin)
apps/models.py: Just make image part of the app itself. No foreign keys needed.
class App(models.Model):
name = models.CharField(max_length=200)
# ...
image = models.ImageField(upload_to = "apps")
apps/views.py: Now the view just has one object to loop through. No weird sql queries needed.
def index(request):
latest_apps_list = App.objects.all().exclude(approved=False).order_by('name')[:20]
t = loader.get_template('apps/index.html')
c = Context({
'latest_apps_list': latest_apps_list,
})
return HttpResponse(t.render(c))
you should zip them in the view, and pass that zipped object to the template, and then iterate through them.
view:
def index(request):
latest_apps_list = list(App.objects.all().exclude(approved=False).order_by('name')[:20])
app_images = Image.objects.filter(app__in=latest_apps_list).order_by('app__name')[:20]
t = loader.get_template('apps/index.html')
c = Context({
'zipped_app_list': zip(latest_apps_list, list(app_images))
})
return HttpResponse(t.render(c))
template:
{% for app, image in zipped_app_list %}
{{ app }}
{{ image}}
{% endfor %}

Categories

Resources