by Łukasz Rekucki (lrekucki@gmail.com)
Student at University of Warsaw (MISMaP): Computer Science and Mathematics.
Got sucked into Django world (thanks to Marek Stępniowski) to work on open-source project Wolnelektury.pl.
Joined a promising Polish start-up called Smartupz.com.
My first patch got commited to Django code base. Yey!
Still working at Smartupz. Trying to get more patches into Django so that my daily work is easier.
Make a quick note about who paid for the trip.
Request
together with
arguments from URL resolver and returns a Response
.
1 def article_list(request):
2 articles = Article.objects.all()
3 return render_to_response("article_list.html", {"articles": articles},
4 context_instance=RequestContext(request))
Talk about how to extend this view to be more reusable
1 def object_list(request, queryset, paginate_by=None, page=None,
2 allow_empty=True, template_name=None, template_loader=loader,
3 extra_context=None, context_processors=None, template_object_name='object',
4 mimetype=None):
5 if extra_context is None: extra_context = {}
6 queryset = queryset._clone()
7 if paginate_by:
8 paginator = Paginator(queryset, paginate_by, allow_empty_first_page=allow_empty)
9 if not page:
10 page = request.GET.get('page', 1)
11 try:
12 page_number = int(page)
13 except ValueError:
14 if page == 'last':
15 page_number = paginator.num_pages
16 else:
17 # Page is not 'last', nor can it be converted to an int.
18 raise Http404
19 try:
20 page_obj = paginator.page(page_number)
21 except InvalidPage:
22 raise Http404
23 c = RequestContext(request, {
24 '%s_list' % template_object_name: page_obj.object_list,
25 'paginator': paginator,
26 'page_obj': page_obj,
27 'is_paginated': page_obj.has_other_pages(),
28 }, context_processors)
29 else:
30 c = RequestContext(request, {
31 '%s_list' % template_object_name: queryset,
32 'paginator': None,
33 'page_obj': None,
34 'is_paginated': False,
35 }, context_processors)
36 if not allow_empty and len(queryset) == 0:
37 raise Http404
38 for key, value in extra_context.items():
39 if callable(value):
40 c[key] = value()
41 else:
42 c[key] = value
43 if not template_name:
44 model = queryset.model
45 template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())
46 t = template_loader.get_template(template_name)
47 return HttpResponse(t.render(c), mimetype=mimetype)
Talk about how this view is incomplete: different Paginator, Jinja2 templates, GET field for page.
1 def twitter_login_done(request):
2 request_token = request.session.get('request_token', None)
3 verifier = request.GET.get('oauth_verifier', None)
4 denied = request.GET.get('denied', None)
5 # If we've been denied, put them back to the signin page
6 # They probably meant to sign in with facebook >:D
7 if denied:
8 return HttpResponseRedirect(reverse("socialauth_login_page"))
9 # If there is no request_token for session,
10 # Means we didn't redirect user to twitter
11 if not request_token:
12 # Redirect the user to the login page,
13 return HttpResponseRedirect(reverse("socialauth_login_page"))
14 token = oauth.Token.from_string(request_token)
15 # If the token from session and token from twitter does not match
16 # means something bad happened to tokens
17 if token.key != request.GET.get('oauth_token', 'no-token'):
18 del_dict_key(request.session, 'request_token')
19 # Redirect the user to the login page
20 return HttpResponseRedirect(reverse("socialauth_login_page"))
21 try:
22 twitter = oauthtwitter.TwitterOAuthClient(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET)
23 access_token = twitter.fetch_access_token(token, verifier)
24 request.session['access_token'] = access_token.to_string()
25 user = authenticate(twitter_access_token=access_token)
26 except:
27 user = None
28 # if user is authenticated then login user
29 if user:
30 login(request, user)
31 else:
32 # We were not able to authenticate user
33 # Redirect to login page
34 del_dict_key(request.session, 'access_token')
35 del_dict_key(request.session, 'request_token')
36 return HttpResponseRedirect(reverse('socialauth_login_page'))
37 # authentication was successful, use is now logged in
38 next = request.session.get('twitter_login_next', None)
39 if next:
40 del_dict_key(request.session, 'twitter_login_next')
41 return HttpResponseRedirect(next)
42 else:
43 return HttpResponseRedirect(LOGIN_REDIRECT_URL)
Magic
Request
together with
arguments from URL resolver and returns a Response
.
In Python, any object with a __call__
attribute is a Callable, e.g:
__call__
methodView
class aka Bikeshed
There are many ways to implement the base class. See the Wiki.
They all have pros and cons, but most differences are purely cosmetic. This made it the major topic for bikeshedding on django–developers (~200 posts last year).
Finally, a concesus was reached:
1 class View(object):
2
3 @classmethod
4 def as_view(cls, **initargs):
5 def real_view(*args, **kwargs):
6 instance = cls(**initargs)
7 return cls.dispatch(*args, **kwargs)
8 return real_view
9
10 # urls.py
11 url('^sample-view/$', View.as_view(), name="sample_view")
1 class ArticleList(View):
2 template_name = "article_list.html"
3
4 def get(self, request, *args, **kwargs):
5 self.article_list = self.get_queryset()
6 return self.render_to_response(
7 self.get_context_data(articles=self.article_list))
8
9 def render_to_response(self, context):
10 return render_to_response(self.get_template_names(),
11 context, RequestContext(self.request))
12
13 def get_queryset(self):
14 return Article.objects.all()
15
16 def get_template_names(self):
17 return [self.template_name] if self.template_name is not None else []
18
19 def get_context_data(self, **kwargs):
20 return kwargs
21
22 # urls.py
23 url('^articles/$', ArticleList.as_view(template_name="alternative.html"))
You can compose mixins to reuse common functionality:
1 from django.views.generic.base import TemplateResponseMixin, View
2 from django.views.generic.list import ListView, MultipleObjectMixin
3
4 class ArticleList(TemplateResponseMixin, MultipleObjectMixin, View):
5 model = Article
6 template_name = "articles/article_list.html"
7
8 def get(self, request, *args, **kwargs):
9 self.object_list = self.get_queryset()
10 context = self.get_context_data(object_list=self.object_list)
11 return self.render_to_response(context)
12
13 class ArticleListTwo(ListView):
14 model = Article
You can also use mixins to override functionality:
1 class JSONArticles(JSONMixin, ArticleList):
2 pass
All function-based views have been migrated to CBVs.
direct_to_template
→ TemplateView
redirect_to
→ RedirectView
object_list
→ ListView
object_detail
→ DetailView
create_object
→ CreateView
archive_year
→ YearArchiveView
Together with some useful mixins, e.g:
FormMixin
- form validation methodsModelFormMixin
- extends FormMixin
to work with ModelForms. 1 from django.views.generic import CreateView
2 from myapp.signals import custom_signal
3
4 class CreateArticleView(CreateView):
5
6 def get_form_kwargs(self):
7 kwargs = super(CreateArticleView, self).get_form_kwargs()
8 # by default this will be 'data', 'files' and 'initial'
9 kwargs['author'] = self.request.user
10 return kwargs
11
12 def form_valid(self, form):
13 """Called when the form is valid"""
14 response = super(CreateArticleView, self).form_valid(form)
15 if self.object:
16 custom_signal.send(sender=type(self.object), instance=self.object)
17 # .. or add a Celery task, a message, etc.
18 return response
1 class DelayedModelFormMixin(ModelFormMixin):
2
3 def form_valid(self, form):
4 self.object = form.save(commit=False)
5 self.prepare_object_for_save(self.object)
6 self.object.save()
7 self.object.save_m2m()
8 # emit the signal here...
9 return super(ModelFormMixin, self).form_valid(form)
10
11 def prepare_object_for_save(self, obj):
12 pass
13
14 class CreateCommentView(DelayedModelFormMixin, CreateView):
15
16 def prepare_object_for_save(self, obj)
17 obj.author = self.request.user
Because the as_view()
method produces an ordinary function, you can
apply function decorators to the result:
1 from django.views.generic import TemplateView
2 from django.contrib.auth.decorators import login_required
3
4 url(r'^/protected/$', login_required(
5 TemplateView.as_view(template_name="secret.html")))
This is simple, but after decorating you're left with a function, so subclassing is not possible.
Django provides an easy way to create method decorators:
1 from django.utils.decorators import method_decorator
2
3 class ProtectedView(View):
4
5 @method_decorator(login_required):
6 def dispatch(self, request, *args, **kwargs):
7 return super(ProtectedView, self).dispatch(request, *args, **kwargs)
Note: Any attributes on dispatch()
will get copied
to the function returned by as_view()
, so that csrf_exempt
decorator works correctly.
You can also decorate get()
, post()
, put()
and delete()
this way, but
csrf_exempt
won't work with those methods.
This actually didn't make it to Django 1.3, but I think it's very useful (requires Python 2.6):
1 def view_decorator(fdec)
2 def decorator(cls):
3 original = cls.as_view.im_func
4
5 @functools.wraps(original)
6 def as_view(current, **initkwargs):
7 return fdec(original(current, **initkwargs))
8
9 cls.as_view = classmethod(as_view)
10 return cls
11 return decorator
12
13 # usage:
14
15 @view_decorator(login_required)
16 class ProtectedView(TemplateView):
17 pass
18
19 # but **DO NOT** do this:
20
21 ProtectedView = view_decorator(login_required)(TemplateView)
There are no strict rules when to use one over other.
_why
.Table of Contents | t |
---|---|
Source Files | s |
Slide Numbers | n |
Notes | 2 |
Help | h |