by Łukasz Rekucki (lrekucki@gmail.com)
http://lqc.github.com/djangopiwo/april2011/
Student Kolegium MISMAP na UW (Informatyka, Matematyka i trochę Fizyki)
Zacząłem pracować razem z Marekiem Stępniowskim nad projektem Wolnelektury.pl.
Dołączyłem do zespołu Smartupz.com.
Mój pierszy patch przyjęty do Django. Yey! (nic wielkiego, ale zawsze :P)
Nadal w Smartupz i nadal pracuję w Django.
[Porozmawiaj ze mną o integracji z Babel :)]
WSGIRequest
oraz parametry
dopasowane do ścieżki URL i zwraca HTTPResponse
.
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
Callable
, który bierze jako argument WSGIRequest
oraz parametry
dopasowane do ścieżki URL i zwraca HTTPResponse
.
W Pythonie, dowolny obiekt może być wywoływalny, np.:
__call__
View
Istnieje wiele różnorodnych podejść do problemu. Większość opisana na wiki.
__call__()
and copy()
(bad!)__new__()
(good!)classmethod
classmethod2
(winner!)Każde ma swoje wady i zalety. Jednak większość różnic jest czysto kosmetyczna → Ponad 200 wiadomości na django–developers na ten temat. Nie wszystkie bardzo wartościowe.
View
1 class View(object):
2
3 @classonlymethod
4 def as_view(cls, **initargs):
5 def view(*args, **kwargs):
6 self = cls(**initargs)
7 return self.dispatch(*args, **kwargs)
8 update_wrapper(view, cls, updated=())
9 update_wrapper(view, cls.dispatch, assigned=())
10 return view
11
12 def dispatch(self, request, *args, **kwargs):
13 if request.method.lower() in self.http_method_names:
14 handler = getattr(self, request.method.lower(),
15 self.http_method_not_allowed)
16 else:
17 handler = self.http_method_not_allowed
18 self.request = request
19 self.args = args
20 self.kwargs = kwargs
21 return handler(request, *args, **kwargs)
22
23 # urls.py
24 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"))
Wielodziedziczenie pomaga w ponownym wykorzystaniu kodu:
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
Można też nadpisać funkcjonalność, a nie tylko rozszerzać:
1 class JSONArticles(JSONMixin, ArticleList):
2 pass
Wszystkie dotychczasowe generyczne widoki zostały zmigrowane do CBV.
direct_to_template
→ TemplateView
redirect_to
→ RedirectView
object_list
→ ListView
object_detail
→ DetailView
create_object
→ CreateView
archive_year
→ YearArchiveView
Są one posklejane z mixinów takich jak:
FormMixin
- walidacja formularzy, przetwarzanie GET/POSTModelFormMixin
- j.w. ale działa z 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 # domyślnie: 'data', 'files' i '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 # dodaj `message`, dodaj zadanie do Celery, itp.
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 # wyślij sygnał...
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
Ponieważ metoda as_view()
zwraca zwyczajną funkcję, można jej używać ze zwykłymi dekoratorami:
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")))
Jest to najprostsze podejście, jednak po dekoracji dostajemy funkcję. Nie da się w ten sposób zintegrować funkcjonalności na stałe z klasą tak jak to było w przypadku mixinów.
Django ma ukryty skarb
, który pozwala łatwo dekorować metody:
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: Dekoratory takie jak csrf_except
działają poprzez ustawienie atrybutu na widoku, który jest później
sprawdzany przez middleware. Aby dało się ich używać z dispatch()
, klasa View
kopiuję wszystkie atrybuty z tej
metody na funkcję zwracaną przez as_view()
.
Można też dekorować inne metody tj. get()
, post()
, put()
czy delete()
, ale
csrf_exempt
nie będzie na nich działał (i dobrze!).
Niestety ten kod nie znalazł się w Django 1.3, ale jest bardzo użyteczny (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 # użycie:
14
15 @view_decorator(login_required)
16 class ProtectedView(TemplateView):
17 pass
18
19 # *UWAGA*: można sobie strzelić w stopę
20
21 ProtectedView = view_decorator(login_required)(TemplateView)
_why
.Table of Contents | t |
---|---|
Source Files | s |
Slide Numbers | n |
Notes | 2 |
Help | h |