How to use model methods in django templates with class based view - python

I've defined methods in my model, and I'm trying to use it in django template, which is rendered using ListView
My model looks like this:
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
def get_total_sum(self):
return super().objects.all().filter(user=self.user).aggregate(models.Sum('price'))
My view:
from django.views.generic.list import ListView
from book.models import Book
class BookView(ListView):
template_name = 'book.html'
# I'm using this to order book by created date
def get_queryset(self):
return Book.objects.filter(user=self.request.user).order_by('-created_at')
And my template:
Total books: {{ object_list|length }}
Total price of all books: # I've no idea how to display them here, when using class based view

One thing you can do is to use custom templatetags:
Follow these steps:
Create a folder templatetags inside your app directory
Create a file inside e.g book_filter.py and __init__.py
Inside book_filter.py copy below filter.
book_filter.py
from django import template
egister = template.Library()
#register.filter
def total_price(amounts):
total = 0
for amount in amounts:
total += amount.price
return total
Now in your html file, do the following:
{% load book_filter %}
Total price of all books: {{object_list|total_price}}
Use this link for reference: custom-template-tags
Hope this helps.

If you want to use Book.get_total_sum in a template, you need to make it a property.
#property
def get_total_sum(self):
return super().objects.all().filter(user=self.user).aggregate(models.Sum('price'))
in the template
{{ book.get_total_sum }}
Another way is to inject the value you want by obtaining it with Python code in the view get_context method and injecting it into the context. For a computed value that is different for each Book instance, this obviously does not work, and a property is ideal. Custom template tags are ideal when the code is not naturally bound to one class as a property

Related

Django filter in a View

I'm just starting on Django, and currently stuck on a seemingly simple requirement/behaviour. I want a page rendered with a filtered set of entries based on the class's ForeignKey, and called from a rendered view of that other class.
A simplified version of my model.py is:
from django.db import models
class BookDay(models.Model):
bookdate = models.DateField()
bookevent = models.CharField(max_length=255)
class BookTime(models.Model):
booktime = models.TimeField()
bookdate = models.ForeignKey(BookDay, on_delete = models.CASCADE)
My view.py reads:
from django.http import HttpResponse
from django.views import generic
from .models import BookDay, BookTime
class DayView(generic.ListView):
template_name = 'booking/days.html'
context_object_name = 'bookdate_list'
def get_queryset(self):
return BookDay.objects.order_by('bookdate')
class TimeView(generic.ListView):
model = BookDay
template_name = 'booking/booktimes.html'
context_object_name = 'booktimes_list'
def get_queryset(self):
return BookTime.objects.filter(bookdate=bookday_id).order_by('booktime')
My urls.py contains:
from django.urls import path
from . import views
app_name = 'booking'
urlpatterns = [
path('', views.DayView.as_view(), name='bookdays'),
path('<int:pk>/', views.TimeView.as_view(), name='booktimes'),
]
The referenced days.html renders a set of links per this fragment:
{% for entry in bookdate_list %}
<li>{{ entry.bookevent }}</li>
{% endfor %}
On clicking any of the resulting links, the failure manifests as name 'bookday_id' is not defined.
I can put a fixed integer in place of bookday_id in views.py above, and it works fine (for that ForeignKey only, obviously). Also, I've played around with the filter() parameter name, the relevant url, and the html extensively to no avail.
How should I parameterise this to take the clicked link and filter the BookTimes entries correctly? Should I use Django-filter for this, or can it be done natively in Django?
Well like the error specifies, there is no bookday_id variable. If I understand it correctly, you are interested in the pk parameter of the URL. You can access these positional and named parameter in the self.args and self.kwargs of the View objects, so you can rewrite it to:
class TimeView(generic.ListView):
model = BookDay
template_name = 'booking/booktimes.html'
context_object_name = 'booktimes_list'
def get_queryset(self):
return BookTime.objects.filter(bookdate_id=self.kwargs['pk']).order_by('booktime')
Since the pk is an int, we thus filter on the bookdate_id (which is an integer here).
I would however advice to rename your bookdate foreignkey to bookday (the name of the model it refers to), since now it creates some confusion with the bookdate field of the BookDay model.

Adding data via Admin not visible in html template until restart server (python manage.py runserver)

Example
models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=200)
admin.py
from django.contrib import admin
from .models import Book
admin.site.register(Book)
class Home(TemplateView):
books = Book.objects.all()
def get_context_data(self, **kwargs):
context ['books'] = self.books
return context
index.html
{% for b in books %}
<h2> {{ b.name}} </h2>
{% endfor %}
If I add a new Book via Django administration I need to restart the server
in order for the new value to be visible
How can I auto-refresh the view if any data is changed via djano admin
without having to restart server (python manage.py runserver)
All help would be appreciated as I'm stuck
can i use refresh_from_db in the view ?
Since you have defined the books queryset as an attribute of the Home class, the query will only be evaluated once - the first time it is iterated over. You need to recreate the queryset on every request
class Home(TemplateView):
def get_context_data(self, **kwargs):
context['books'] = Book.objects.all()
return context
You should not be using a TemplateView here. What you are doing is covered by the List view class. If you use that, and define model, you don't need to override get_context_data at all. So it's just:
class Home(ListView):
model = Book
And that's it. Django will know to query all Books on every request.

How can I properly use related_name in django models to get back the class name?

I have an application called "school" inside one of my django projects.
Below is the code of models.py
from django.db import models
class Student(models.Model):
name = models.CharField(max_length=255)
birthday = models.DateField(blank=True)
class Class(models.Model):
name = models.CharField(max_length=255)
student = models.ForeignKey(Student,related_name='classes',null=True)
def __str__(self):
return self.name
And now, views.py:
from django.shortcuts import render
from .models import *
def test(request):
obj2 = Student.objects.get(name='john')
return render(request,'test/list.html', {'obj2':obj2} )
And finally, my template looks like this:
{% block content %}
<h2>
{{ obj2.classes }}
</h2>
{% endblock %}
In my template, I am using obj2.classes (i.e., responseobject.related_name). I want it to print the class name.
However when I access the site at http://127.0.0.1:8000/shop/ ,
it gives me this output:
shop.Class.None
How will I get the output as only "Class", that is the class name?
Would obj2._meta.get_field('classes').related_model.__name__ do the job? This will work on your view only, not on the template.
def test(request):
obj2 = Student.objects.get(name='john')
classes_name = obj2._meta.get_field('classes').related_model.__name__
return render(request, 'test/list.html',
{'obj2':obj2, 'classes_name': classes_name})
Using this method, you avoid to use obj2.classes, which hits the database to retrieve the object.
You can also get the verbose_name with obj2._meta.get_field('classes').related_model._meta.verbose_name.

Fetching a random item from the view/object list using Django

UPDATE #2
Status: Still not solved
Updated: Thurs. Dec. 18, 11:30 a.m.
I'm currently using FullArticle.objects.order_by('?').first() to get a random article from my database, but it's not working. There is probably something missing from my models, view or url.py that's missing.
models.py
from django.db import models
from django.core.urlresolvers import reverse
# Create your models here.
class FullArticleQuerySet(models.QuerySet):
def published(self):
return self.filter(publish=True)
class FullArticle(models.Model):
title = models.CharField(max_length=150)
author = models.CharField(max_length=150)
slug = models.SlugField(max_length=200, unique=True)
pubDate = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
category = models.CharField(max_length=150)
heroImage = models.CharField(max_length=250, blank=True)
relatedImage = models.CharField(max_length=250, blank=True)
body = models.TextField()
publish = models.BooleanField(default=True)
gameRank = models.CharField(max_length=150, blank=True, null=True)
objects = FullArticleQuerySet.as_manager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("FullArticle_detailed", kwargs={"slug": self.slug})
class Meta:
verbose_name = "Blog entry"
verbose_name_plural = "Blog Entries"
ordering = ["-pubDate"]
views.py
from django.views import generic
from . import models
from .models import FullArticle
# Create your views here.
class BlogIndex(generic.ListView):
queryset = models.FullArticle.objects.published()
template_name = "list.html"
randomArticle = FullArticle.objects.order_by('?').first()
class BlogDetail(generic.DetailView):
model = models.FullArticle
template_name = "detailed.html"
urls.py
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns(
'',
url(r'^$', views.BlogIndex.as_view(), name="list"),
url(r'^(?P<slug>\S+)', views.BlogDetail.as_view(), name="detailed"),
)
Section in list.html that I want to be random
<div class="mainContent clearfix">
<div class="wrapper">
<h1>Top 10 Video Games</h1>
{% for article in object_list|slice:":1" %}
<p class="date">{{article.pubDate|date:"l, F j, Y" }}</p> | <p class="author">{{article.author}}</p>
<img src="{{article.heroImage}}" alt="" class="mediumImage">
<p class="caption">{{article.body|truncatewords:"80"}}</p>
{% endfor %}
I assume that FullArticle.objects.order_by('?')[0] will give me a
random item from my class of FullArticle. But, let's say that out of
my model, I only want data associated with the specific parts of the
article: title, author, heroImage and body. How would I go about doing
that?
To get specific fields of an object, use values or values_list. The first will return dictionaries, the second tuples:
FullArticle.objects.order_by('?').values('title','author','heroImage','body').first()
The above would result in something like:
{'title': 'Foo', 'author': 'Foo Author', ... }
I've also tacked on your suggestion of random =
FullArticle.objects.order_by('?')[0] called it "random" instead.
Not sure what this is about, but try to avoid shadowing built-in libraries, like random.
1) Actually you almost did it.
try:
article = FullArticle.objects.order_by('?')[0]
except IndexError:
article = None
2) You could use this in models.py as well as in views.py. IMHO there is no need to extract this string to separate method so I would write this code wherever I need it.
3) Better use ORM don't convert db result to list to choose first item. This is can be really memory and CPU expensive.
Getting a random article would usually be done in a view, or as a modelmanager method, or as a class method. Fullarticle.random should not be a class attribute. That will not work as you expect.
# Used in a view.
article = FullArticle.objects.order_by('?').first()
# you can also make a random() method in your model manager.
def random(self):
return self.get_queryset().order_by('?').first()
# or as a class method inside FullArticle
#classmethod
def random(cls):
return cls.objects.order_by('?').first()
I'm not quite sure what exactly you mean by this.
I only want data associated with the specific parts of the article: title, author, heroImage and body. How would I go about doing that?
To access specific attributes you do this:
title = article.title
author = article.author
If you don't need to use article.category, just don't access it.
from django.views.generic import DetailView
from books.models import Publisher, Book
To pass data from your (class based) View to the template it has to be added to the context.
Here's an example from the official documentation:
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
source: https://docs.djangoproject.com/en/1.7/topics/class-based-views/generic-display/#adding-extra-context
Lots of people find Class Based Views in Django to be a bit confusing. I would recommend that you understand how function based views work before you start doing anything complicated with CBVs.

Django - logic behind displaying relational tables in template

I have multiple related tables defined in my Django models:
# first models.py
from django.db import models
class Character(models.Model):
first_field = models.DateTimeField()
second_field = models.TextField()
# second models.py
from django.db import models
class Op(models.Model):
fk_character = models.ForeignKey('Character')
some_field = models.DateTimeField()
other_field = models.TextField()
class Participant(models.Model):
fk_op = models.ForeignKey('Op')
fk_character = models.ForeignKey('Character')
some_other_field = models.IntegerField(default=0)
For now, I'm sending this data from a view to template in a way like this:
# views.py
from django.shortcuts import render_to_response
from django.template import RequestContext
from second.models import MainModel
def home(request):
data = Op.objects.filter(some_field__isnull=True).order_by('-date')
rc = RequestContext(request, {'data':data})
return render_to_response('index.html', rc)
In this way I do have all the Op related data I need in my index.html template, but I'm struggling with logic to display this data in my template in a specific way. For example:
display a list of all Ops,
for each list item, check if Character is also a Participant in current Op item,
if it isn't, display some button, if it is than don't display the button
I know that template shouldn't handle any programming logic, but I'm also not sure what would be the best approach to solve this. Should I do all the logic in my view and construct a new object and send that object to my view or is there an easy way to solve this in template with current object I'm sending?
Update your model:
class Op(models.Model):
fk_character = models.ForeignKey('Character')
some_field = models.DateTimeField()
other_field = models.TextField()
def check_if_participant(self):
return bool(self.participant_set.all())
Display list of all Ops:
{% for op in data %}
{{op.some_field}}
{% if op.check_if_participant %}Yes - Character is participant {% endif %}
{% endfor %}

Categories