TalkList Views¶
Our TalkLists need a few different views, so let’s look at creating those.
TalkListListView¶
We should have a view to show all of our lists. In views.py:
1 2 3 4 5 6 7 8 9 10 | [...]
from braces import views
from . import models
class TalkListListView(views.LoginRequiredMixin, generic.ListView):
model = models.TalkList
def get_queryset(self):
return self.request.user.lists.all()
|
There’s not really anything fancy about this view other than overriding get_queryset. We want users to only be able to view lists that they own, so this does that for us. We didn’t specify a template, so Django will look for the default one at talks/talklist_list.html.
Template¶
Since this is an app and the templates are only for this app, I think it’s best to put them in the app. This makes it easier to focus on the files for a specific app and it also makes it easier to make an app reusable elsewhere.
mkdir -p talks/templates/talks
touch talks/templates/talks/talklist_list.html
We need to namespace the template inside of a directory named the same as our app. Open up the template file and add in:
{% extends '_layouts/base.html' %}
{% block title %}Lists | {{ block.super }}{% endblock title %}
{% block headline %}<h1>Your Lists</h1>{% endblock headline %}
{% block content %}
<div class="row">
<div class="col-sm-6">
<ul class="list-group">
{% for object in object_list %}
<li class="list-group-item">
<a href="{{ object.get_absolute_url }}">{{ object }}</a>
</li>
{% empty %}
<li class="list-group-item">You have no lists</li>
{% endfor %}
</ul>
</div>
<div class="col-sm-6">
<p><a href="#" class="btn">Create a new list</a></p>
</div>
</div>
{% endblock %}
URL¶
Now in our urls.py file, we need to update our list_patterns set of patterns.
[...]
url(r'^$', views.TalkListListView.as_view(), name='list'),
url(r'^d/(?P<slug>[-\w]+)/$', views.TalkListDetailView.as_view(),
name='detail'),
[...]
You’ll notice that we replaced our old default URL (r'^$') with our TalkListListView and put the TalkListDetailView under a new regex that captures a slug. Our model’s get_absolute_url should still work fine.
TalkListDetailView¶
Let’s build out our actual detail view now. Back in views.py:
1 2 3 4 5 6 7 8 9 10 11 12 | class TalkListDetailView(
views.LoginRequiredMixin,
views.PrefetchRelatedMixin,
generic.DetailView
):
model = models.TalkList
prefetch_related = ('talks',)
def get_queryset(self):
queryset = super(TalkListDetailView, self).get_queryset()
queryset = queryset.filter(user=self.request.user)
return queryset
|
This mixin from django-braces lets us do a prefetch_related on our queryset to, hopefully, save ourselves some time in the database. Again, we didn’t specify a template so we’ll make one where Django expects.
Template¶
Create the file talks/templates/talks/talklist_detail.html and add in:
{% extends '_layouts/base.html' %}
{% block title %}{{ object.name }} | Lists | {{ block.super }}{% endblock title %}
{% block headline %}
<h1>{{ object.name }}</h1>
<h2>Your Lists</h2>
{% endblock headline %}
{% block content %}
<div class="row">
<div class="col-sm-6">
<p>Talks go here</p>
</div>
<div class="col-sm-6">
<p><a href="{% url 'talks:lists:list' %}">Back to lists</a></p>
</div>
</div>
{% endblock %}
A pretty standard Django template. We already have the URL so this should be completely wired up now.
RestrictToUserMixin¶
We had to override get_queryset in both of our views, which is kind of annoying. It would be nice to not have to do that, especially two different ways both times. Let’s write a custom mixin to do this work for us.
class RestrictToUserMixin(views.LoginRequiredMixin):
def get_queryset(self):
queryset = super(RestrictToOwnerMixin, self).get_queryset()
queryset = queryset.filter(user=self.request.user)
return queryset
This does the same work as our get_queryset in TalkListDetailView. Let’s use it. In both views, add RestrictToUserMixin and take out the views.LoginRequiredMixin from django-braces since our new mixin provides that functionality too. Also remove the overrides of get_queryset from both views.
TalkListCreateView¶
We want to be able to create a new TalkList, of course, so let’s create a CreateView for that. In views.py, still, add a new class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [...]
import .forms
class TalkListCreateView(
views.LoginRequiredMixin,
views.SetHeadlineMixin,
generic.CreateView
):
form_class = forms.TalkListForm
headline = 'Create'
model = models.TalkList
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
return super(TalkListCreateView, self).form_valid(form)
|
This view has a form_class that we haven’t created yet, so we’ll need to do that soon. Also, we override form_valid, which is called when the submitted form passes validation, and in there we create an instance in memory, assign the current user to the model instance, and then save for real and call super() on the method.
This view also brings in the SetHeadlineMixin and provides the headline attribute. We do this because we’ll be using the same template for both create and update views and we don’t want them to both have the same title and headline. This way we can control that from the view instead of having to create new templates all the time.
Let’s create the form now.
TalkListForm¶
We don’t yet have a talks/forms.py so go ahead and create it with the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | from __future__ import absolute_import
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, ButtonHolder, Submit
from . import models
class TalkListForm(forms.ModelForm):
class Meta:
fields = ('name',)
model = models.TalkList
def __init__(self, *args, **kwargs):
super(TalkListForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
'name',
ButtonHolder(
Submit('create', 'Create', css_class='btn-primary')
)
)
|
Nothing really different here from our earlier forms except for line 13 which restricts the fields that the form cares about to just the name field.
URL¶
As with all of our other views, we need to make an URL for creating lists. In talks/urls.py, add the following line:
url(r'^create/$', views.TalkListCreateView.as_view(), name='create'),
Template¶
And, again, we didn’t specify a specific template name so Django expects talks/talklist_form.html to exist. Django will use this form for both CreateView and UpdateView views that use the TalkList model unless we tell it otherwise.
{% extends '_layouts/base.html' %}
{% load crispy_forms_tags %}
{% block title %}{{ headline }} | Lists | {{ block.super }}{% endblock title %}
{% block headline %}
<h1>{{ headline }}</h1>
<h2>Your Lists</h2>
{% endblock headline %}
{% block content %}
{% crispy form %}
{% endblock content %}
You can see here were we use the {{ headline }} context item provided by the SetHeadlineMixin. Now users should be able to create new lists.
TalkListUpdateView¶
Anything we can create, we should be able to update so let’s create a TalkListUpdateView in views.py.
class TalkListUpdateView(
RestrictToOwnerMixin,
views.LoginRequiredMixin,
views.SetHeadlineMixin,
generic.UpdateView
):
form_class = forms.TalkListForm
headline = 'Update'
model = models.TalkList
There isn’t anything in this view that we haven’t covered already. All that’s left for it is to create the URL pattern.
URL¶
You should be getting the hang of this by now, so let’s just add this line to our urls.py:
url(r'^update/(?P<slug>[-\w]+)/$', views.TalkListUpdateView.as_view(),
name='update'),
Global template changes¶
It’s great that we’ve created all of these views but now there’s no easy way to get to your views. Let’s fix that by adding the following into _layouts/base.html next to our other navigation items inside the {% else %} clause:
<a href="{% url 'talks:lists:list' %}" class="btn btn-primary navbar-btn">Talk lists</a>
App template changes¶
Our talks/talklist_list.html template should have a link to the TalkListCreateView so let’s add that into the sidebar:
<p><a href="{% url 'talks:lists:create' %}" class="btn">Create a new list</a></p>
DeleteView?¶
So what about a DeleteView for TalkList? I didn’t make one for this example but it shouldn’t be too hard of an exercise for the reader. Be sure to restrict the queryset to the logged-in user.