I am learning following Django 1.7 tutorial in https://docs.djangoproject.com/en/1.7/intro/tutorial04/.
I copied code from web, then run it. I encounter two question:
After adding IndexView which inherits ListView, the 127.0.0.1:8000/polls page just returns "No polls are available." without any database items. I am confused by classed based view, seems the context value is not passed to template.
----------> solved, this is my silly type mistake.
get_queryset(), what is for, when this method is called, how does this map to context? How does this function knows context mapping, what if there are two contexts and two values?
Could someone give me some guide here? thanks a lot.
polls/urls.py
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns('',
url(r'^$', views.IndexView.as_view(), name='index'),
#url(r'^$', views.index, name='index'), #this url works
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
url(r'^(?P<pk>\d+)/results/$', views.ResultsView.as_view(), name='results'),
url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'),
)
polls/views.py
from django.http import HttpResponse, Http404,HttpResponseRedirect
from django.template import RequestContext, loader
from django.shortcuts import render,get_object_or_404
from .models import Question, Choice
from django.core.urlresolvers import reverse
from django.views import generic
class IndexView(generic.ListView):
template_name ='polls/index.html'
context_object_name = 'last_question_list'
def get_queryset(self):
return Question.objects.order_by('-pub_date')[:5]
#this works
#def index(request):
# latest_question_list = Question.objects.order_by('-pub_date')[:5]
# context = {'latest_question_list': latest_question_list}
# return render(request, 'polls/index.html', context)
polls/index.html
{% if latest_question_list %} <!-- seems here value is not passed by -->
<ul>
{% for question in latest_question_list %}
<li>
{{ question.question_text }}</li>
{% endfor %}
</ul>
{% else %}
<p>{{ latest_question_list }}</p>
<p>No polls are available.</p> <!-- always display this -->
<p>{{ latest_question_list }}</p>
{% endif %}
database
>>> from polls.models import Question
>>> Question.objects.all()
[<Question: What's up?>, <Question: tttt>]
Since Django is Open Source, it is possible to determine, how queryset becomes available in template via context_object_name variable. Let's take a look at django.views.generic.list.py:
class MultipleObjectMixin(ContextMixin):
# Some fields
context_object_name = None
def get_queryset(self):
# get_queryset implementation
def get_context_object_name(self, object_list):
"""
Get the name of the item to be used in the context.
"""
if self.context_object_name:
return self.context_object_name
elif hasattr(object_list, 'model'):
return '%s_list' % object_list.model._meta.model_name
else:
return None
def get_context_data(self, **kwargs):
queryset = kwargs.pop('object_list', self.object_list)
# Some stuff
context_object_name = self.get_context_object_name(queryset)
# Some other stuff
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super(MultipleObjectMixin, self).get_context_data(**context)
# And, of course, a lot of other functions
class BaseListView(MultipleObjectMixin, View):
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
# Some magic
context = self.get_context_data()
return self.render_to_response(context)
# Skipped
When you are calling get method, Django initializes self.object_list with get_queryset(). Then, it calls get_context_data() and passes the context data to a template.
MultipleObjectMixin.get_context_data returns a dictionary with name->variable pairs. It creates a pair get_context_object_name() -> object_list, and that is why you can access your list using defined context_object_name name.
Class based views make the task of the developer a lot easier by providing a lot of pre-baked code. ListView is used to return a list of objects. ListView like all other views is extended from View. View implements a get_context_data method which is passes data to be used within the template. So, now if examine the get_context_data method of ListView, we find this:
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
queryset = kwargs.pop('object_list', self.object_list)
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super(MultipleObjectMixin, self).get_context_data(**context)
As you can see, object_list is set to queryset or if you provide context_object_name, it is set to queryset. And queryset is set by the get_queryset defined by you.
I hope this makes the process clear.
Related
I really dislike django CBV design which makes things without flexibility.
I would like to have a page whose upper part showing the content of objects and lower part has a form to be posted.
CBS formview
class EditStudent(FormView):
template_name = "editstudent.html"
model = models.Student
success_url = "/home"
Update:
I add the method but the error
'NoneType' object is not callable shows up.
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['students'] = Student.objects.all()[:5]
return context
How can I retriev objects of studens and show them on the template.
Thanks.
'NoneType' object is not callable: I get this error when I don't specify a form class. Created form class 'StForm' associated with model 'Student'.
In the EditStudent view class, the CreateView class was inherited, since the data was not saved to the database with the FormView.
Replace bboard with the name of the folder where your templates are placed.
I have this: templates/bboard which are in the application folder.
template_name = 'bboard/tam_form.html'
The success_url row specifies a path based on the path name.
success_url = reverse_lazy('student')
The five most recent records are also transmitted in the context.
context['students'] = Student.objects.order_by('-pk')[:5]
In the template, the first five records are displayed on top and a form is displayed below to fill out.
forms.py
from django.forms import ModelForm
from .models import Student
class StForm(ModelForm):
class Meta:
model = Student
fields = '__all__'
views.py
from .models import Student
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
from .forms import StForm
class EditStudent(CreateView):
template_name = 'bboard/editstudent.html'
form_class = StForm
success_url = reverse_lazy('student')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['students'] = Student.objects.order_by('-pk')[:5]
return context
urls.py
from django.urls import path
from .views import *
urlpatterns = [
path('student/', EditStudent.as_view(), name='student'),
]
editstudent.html
<h4>
{% for aaa in students %}
<p>{{ aaa }}</p>
{% endfor %}
</h4>
<h2>form</h2>
<form method="post" action="{% url 'student' %}">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="adding">
</form>
I'm still getting used to class based views, while I get the general purpose, some things still get past me. I'm following a tutorial which basically walks you through the motions but tends to ignore the fuzzy details, such as this piece of code:
class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView):
"""Generic class-based view listing books on loan to current user."""
model = BookInstance
template_name ='books/bookinstance_list_borrowed_user.html'
paginate_by = 1
def get_queryset(self):
return BookInstance.objects.filter(
borrower=self.request.user
).filter(status__exact='o').order_by('due_back')
I get the model, template_name and paginate_by parts, they're attributes of the ListView class, but what I don't get is the get_queryset part, where is it executed? As seen in the code below it's called nowhere. Where is it returned to? I guess my first question can be chalked down to "What do functions in class based views do?"
{% extends "base_generic.html" %}
{% block content %}
<h1>Borrowed books</h1>
{% if bookinstance_list %}
<ul>
{% for bookinst in bookinstance_list %}
<li class="{% if bookinst.is_overdue %}text-danger{% endif %}">
{{bookinst.book.title}} ({{ bookinst.due_back }})
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no books borrowed.</p>
{% endif %}
So, two issues, first, where did the get_queryset return to, and second, what is
bookinstance_list? It's not a context variable, but seems to be used out of the blue, why is this variable usable?
Class Based Views calls get_queryset() in the get() method of your view, I'll present some example code from Django 1.11.
# django/views/generic/list.py:159
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
...
context = self.get_context_data()
return self.render_to_response(context)
The ListView class sets self.object_list = self.get_queryset() in this method, however this doesn't explain where it sets it in the context passed to your template. If we take a quick look at get_context_data():
# django/views/generic/list.py:127
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
queryset = kwargs.pop('object_list', self.object_list)
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super(MultipleObjectMixin, self).get_context_data(**context)
context is assigned a dictionary with 'object_list': queryset, so when you're trying to access the resulting QuerySet from get_queryset in your template you should access object_list.
The Django documentation on class-based generic views has a section on extending your context data with additional info. https://docs.djangoproject.com/en/2.1/topics/class-based-views/generic-display/#dynamic-filtering
I have this method on the view page. It works fine and shows everything correctly but I want to convert it into a generic list view so that I could apply pagination to it.
Here is the function :`
#views.py
def index(request):
all_artists = Artist.objects.all()
all_songs = Song.objects.all()
all_albums = Album.objects.all()
return render(request, 'index.html', {'all_albums':all_albums,'all_songs':all_songs, 'all_artists':all_artists})
So I followed some tutorials and ended up with this:
#new views.py
class IndexView(ListView):
template_name = 'index.html'
context_object_name = 'home_list'
queryset = Artist.objects.all()
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['all_artists']=Artist.objects.all()
context['all_songs']=Song.objects.all()
context['all_albums']=Album.objects.all()
Though it compiles without any error, when I render the page, the context object does not gets rendered.
Your help is very much appreciated!
Thankyou
EDIT(13-APR-17):
Thankyou guys! It finally worked with some little modifications.
class IndexView(generic.ListView):
template_name = 'index.html'
context_object_name = 'home_list'
queryset = Artist.objects.all()
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['all_artists']=Artist.objects.all()
context['all_songs']=Song.objects.all()
context['all_albums']=Album.objects.all()
return context
enter code here
enter code here
urls.py
in urls.py from your django app you need to include a url that references to your views and include this urls.py to your django project main urls.py.
#urls.py
from django.conf.urls import url
from .views import IndexView
urlpatterns = [
url(r'^path/$', IndexView.as_view(), name="index"),
]
Then in your views.py override the variable paginate_by
#views.py
class IndexView(ListView):
template_name = 'index.html'
context_object_name = 'home_list'
queryset = Artist.objects.all()
paginate_by = 10 # Number of objects for each page
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['all_artists']=Artist.objects.all()
context['all_songs']=Song.objects.all()
context['all_albums']=Album.objects.all()
return context
Finally in your index.html
add the pagination {% pagination_for page_obj %}
{% block content %}
<!--some content -->
<!--begin paginator -->
{% pagination_for page_obj %}
<!--end paginator-->
{% endblock %}
Your get_context_data() needs to return the context. So if this is your exact code, you need to add return context to it
I have a block of code on my template which does not show up on my html nor does it give any error on Chromes console. I am trying to display a list of images which when clicked takes you to the detail of that image.
Here is the key part of my HTML(base.html):
<div class="container-fluid">
<h2>Popular</h2> #only this shows up
{% for obj in object_list %}
<img src = "{{ obj.mpost.url}}" width="300"><br>
<a href='/m/{{ obj.id }}/'> {{obj.username}} </a><br/>
{% endfor %}
</div>
views.py:
from django.shortcuts import render,get_object_or_404
from .models import m
# Create your views here.
def home(request):
return render(request,"base.html",{})
def m_detail(request,id=None):
instance = get_object_or_404(m,id=id)
context = {
"mpost": instance.mpost,
"instance": instance
}
return render(request,"m_detail.html",context)
def meme_list(request): #list items not showing
queryset = m.objects.all()
context = {
"object_list": queryset,
}
return render(request, "base.html", context)
urls.py:
urlpatterns = [
url(r'^$', home, name='home'),
url(r'^m/(?P<id>\d+)/$', m_detail, name='detail'),#issue or not?
]
models.py:
class m(models.Model):
username = "anonymous"
mpost = models.ImageField(upload_to='anon_m')
creation_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return m.username
Thank you so much for your time. :)
I assume the problem is the fact that you don't have a url at all for the meme list, so either you're showing the home view and need to move the code from the meme_list view into your home view, or you need to make a url for the meme_list (and navigate to that new url instead)
I've only been at python for about 2 weeks now and I've run into an issue which I've been trying to figure out for the past 3 days. I've read through the official documentation and pretty much every tutorial and youtube video available.
I'm trying to build a simple blog as a first project and would like to have a section where people can post comments. I have set up the comments model and a modelform to go along with it. However, I can't figure out how to get django to create the form for me in the template, nothing is displayed.
models.py
from django.db import models
from django.forms import ModelForm
class posts(models.Model):
author = models.CharField(max_length = 30)
title = models.CharField(max_length = 100)
bodytext = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.title
class comment(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
author = models.CharField(max_length = 30)
body = models.TextField()
post_id = models.ForeignKey('posts')
def __unicode__(self):
return self.body
class commentform(ModelForm):
class Meta:
model = comment
views.py
from django.shortcuts import render_to_response
from blog.models import posts, comment, commentform
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.context_processors import csrf
def home(request):
entries = posts.objects.all()
return render_to_response('index.html', {'posts' : entries})
def get_post(request, post_id):
post = posts.objects.get(id = post_id)
context = {'post': post}
return render_to_response('post.html', context)
def add_comment(request, post_id):
if request.method == 'POST':
form = commentform(request.POST)
if form.is_valid():
new_comment = form.save(commit = false)
new_comment.post_id = post_id
new_comment.save()
else:
form = commentform()
context = RequestContext(request, {
'form': form})
return render_to_response('post.html', context)
Urls.py
from django.conf.urls import patterns, include, url
from django.conf import settings
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^$', 'blog.views.home', name='home'),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)),
url(r'^(?P<post_id>.*)/$', 'blog.views.get_post'),
url(r'^post_comment/(\d+)/$','blog.view.add_comment'),
post.html
{% extends 'base.html' %}
{% block content %}
<p><h3> {{ post }} </h3></p>
<p> {{ post.bodytext }} </p>
<br><br><br><br>
<form action="/post_comment/{{ post.id }}/" method="POST"> {% csrf_token %}
{{ form }}
<input type="submit" value="Post">
</form>
{% endblock %}
I do get a 403 error CSRF verification failed but, the more pressing issue I think is that the {{ form }} doesn't do anything in the template.
I believe home function and get_post function are working and all the urls are working properly. So, I assume there's something wrong with the add_comment function or in posts.html.
Thanks
Django isn't magic. Your comment form is in a completely different view from the one that displays the blog post. So how is Django supposed to know to render it? You need to actually pass the form object to the template somehow.
The way that this was done in the old contrib.comments app was to have a simple template tag which was responsible for just displaying the comment form for an object, which you could place on any template. The form itself submitted to its own view as usual.
One thing you might check is the syntax of the render_to_response call. I believe you'll want to define it like this. (Maybe your syntax will work too, and this isn't the issue, but I have mostly seen the call made like this). This could be the cause of the missing form in the context.
return render_to_response('post.html',
{'form': form},
context_instance=RequestContext(request))
Let me know if this works. Hope this helps,
Joe
Reference: https://docs.djangoproject.com/en/dev/topics/http/shortcuts/#django.shortcuts.render_to_response