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
Related
How would I take a value from a view function and put it in a model field? For example, let’s say I did a view that adds two numbers together. How would take that value and add it to the field of a model every time an instance of that model is created? Or is there another way of doing this that I’m not seeing.
I have a function that takes the user's IP and displays the city and region they are in. I was wondering how I could put that information in a model field every time an instance of that model is created.
I think you are talking about get_context_data() which is a method that allows you to pass data to the template.
For example, here is the code from official docs:
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
You can then access that context in your template:
{% for book in book_list %}
<p>{{ book.name }}</p>
In your example you would pass the IP to the template or the form depending on what you are trying to do.
Here are official docs that explain it
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 %}.
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 %}
I have a django model Story which I am successfully able to index using templates. However there is another model Reviews which has a static method which takes Story object and returns ratings as Integer. How can I index Story on ratings also.
{{ object.story_name }}
{{Reviews.ratings(object)}}
I tried to call this method in template story_text.txt, but that results in an error.
django.template.exceptions.TemplateSyntaxError: Could not parse the remainder: '(object)'....
Edit:
I tried using below in template, it doesn't give any error while building the index. But how can I now refer to this field while searching using SearchQuerySet
Reviews.average_start_rating( {{object}} )
I am confused. I don't think that you can use syntax like {{ Reviews.rating object }} with template engine in Django. If it is possible, that is what I didn't know.
Why don't you pass what you want to show in template in Context in the first place?
{{ object }} could be rendered because it has object in Context. For example, if you use UpdateView(class based view), It contains object in Context automatically.
class Example(UpdateView):
model = yourClass
form_class = yourFormClass
template_name = yourhtml
success_url = URL redirect page after success
you can use {{object}} in yourhtml.html because of UpdateView. you give pk number in url conf like (?P<pk>[0-9]+).
you can do like this without UpdateView
class anotherExample(View):
def get(self, request, *args, **kwargs):
render(request, 'yourhtml.html', {"object": Class.objects.get(id=self.kwargs['pk'])})
in form view, you can use
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['object'] = Class.objects.get(id= ... )
return context
my idea is passing story object and review object which has FK of story object together in context.
I was able to get it working using haystack advanced-data-preparation.
Advanced Data Preparation
Using an additional field one can have a prepare method for that. However only issue is I can order the data using this field but can't search using it.
class StoryIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
ratings = indexes.FloatField()
def prepare_ratings(self, obj):
return Reviews.ratings(obj)
def get_model(self):
return Story
Instead of using a template for the text field, here you can use the prepare or prepare_FOO methods:
class StoryIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True)
# text = indexes.CharField(document=True, use_template=True)
# ratings = indexes.FloatField()
def prepare_text(self, obj):
return "\n".join(f"{col}" for col in [obj.story_name, Reviews.ratings(obj)])
def get_model(self):
return Story
So I have a model Listing() that has a field views. In my one of my views, when someone looks at the listing's page, the views field is incremented by one via listing.views = F('views') + 1 and listing.save(update_fields=['views']). My issue is that when I access the views attribute from that same template using {{ listing.views }}, instead of display the current amount of views, the template displays F(views) + Value(1) (literally that text). Now, I assume I could use a Model method such as def get_views() which will return self.views, but I was wondering why I am getting this weird issue. Also, is there a way without writing a model method that I can get the actual integer instead of the odd F(views) + Value(1)?
Here is my current code:
models.py
class Listing(models.Model):
...
views = models.IntegerField(default=0)
listings.py
class ListingView(View):
def get(self, request):
listing_id = request.GET['listing_id']
listing = get_object_or_404(Listing, id=listing_id)
listing.views = F('views') + 1
listing.save(update_fields=['views'])
return render(request, 'listings.html', {'listing': listing})
listings.html
<html>
{{ listing.views }}
</html>
Using F expressions like this requires you to re-fetch the item once saved in order to get updated values (due to the nature of the F expression updating at a database level and not the model instance itself; perhaps that's where the decrease in operational costs come in).
From the docs -
In order to access the new value that has been saved in this way, the object will need to be reloaded:
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()