$30 off During Our Annual Pro Sale. View Details »

Django Views: Functions, Classes, and Generics

Andrew Pinkham
September 07, 2015

Django Views: Functions, Classes, and Generics

Presented at DjangoCon US 2015.

At the heart of Django is the HTTP Request and Response cycle, powered by URL patterns and views that you must create. But what kind of view should you use? A view function? A class-based view? A generic class-based view? This talk is all about the essence of views, and will explore the differences, advantages and disadvantages of each type of view, finishing with a guide of when to use what.

http://andrewsforge.com/presentation/django-views-functions-classes-generics/

Andrew Pinkham

September 07, 2015
Tweet

More Decks by Andrew Pinkham

Other Decks in Technology

Transcript

  1. ANDREW PINKHAM
    TOPIC
    DATE AUTHOR
    SEPTEMBER 2015
    VIEWS: FUNCTIONS, CLASSES & GENERICS
    DJANGOCON US 2015

    View Slide

  2. $ whoami
    Software Engineer

    Freelance Consultant

    Technical Instructor
    Andrew Pinkham
    Hi, I’m

    View Slide

  3. AUTHOR OF DJANGO UNLEASHED
    AVAILABLE FOR PREORDER; SHIPS DECEMBER 2015

    View Slide

  4. Answers today:
    Django handles HTTP for me! Why do I have to
    worry about it?

    When is Django deprecating function views?

    Aren’t all class-based views generic?

    How do I choose what kind of view to use?

    View Slide

  5. $ ls views
    1_fundamentals

    2_views

    3_tools

    View Slide

  6. $ ls views
    1_fundamentals -> problem, meet solution

    2_views -> examining the solution

    3_tools -> making the solution easier

    View Slide

  7. $ less views/1_fundamentals
    What is a callable?

    What is HTTP, anyway?

    Django's HTTP request/response cycle

    A view’s purpose

    View Slide

  8. $ less views/2_views
    A Brief History of Views

    Functions Views

    Class Based Views

    Generic Class Based Views

    View Slide

  9. $ less views/3_tools
    Enhance!

    When to Use What

    View Slide

  10. afrg.co/views

    View Slide

  11. PYTHON CALLABLES
    UNDERSTANDING THE PROBLEM

    View Slide

  12. def a_function(*args, **kwargs):
    return 'Hello DjangoCon 2015!'
    >>> a_function()
    'Hello DjangoCon 2015!'

    View Slide

  13. >>> lambda_function = lambda: 'Enjoying Austin?'
    >>> lambda_function()
    'Enjoying Austin?'

    View Slide

  14. class FirstClass:
    an_attribute = (
    'Have you visited any good restaurants?')
    def a_method(*args, **kwargs):
    self = args[0]
    return self.an_attribute
    >>> fc_obj = FirstClass()
    >>> fc_obj.a_method()
    'Have you visited any good restaurants?'

    View Slide

  15. class SecondClass(FirstClass):
    def __init__(self):
    """Magic method to initialize a class"""
    self.an_attribute = (
    "Easy Tiger, La Condessa and "
    "Lambert's are my favorites.")
    >>> sc_obj = SecondClass()
    >>> sc_obj.a_method()
    "Easy Tiger, La Condessa and Lambert's are my
    favorites."

    View Slide

  16. class ThirdClass(SecondClass):
    def __call__(*args, **kwargs):
    """Magic method to make object callable"""
    return (
    "I like grabbing ice cream at "
    "Amy's, too.")
    >>> tc_obj = ThirdClass()
    >>> tc_obj()
    "I like grabbing ice cream at Amy's, too."

    View Slide

  17. >>> callable(a_function)
    True
    >>> callable(lambda_function)
    True
    >>> callable(FirstClass)
    True
    >>> callable(fc_obj)
    False
    >>> callable(sc_obj)
    False
    >>> callable(tc_obj)
    True

    View Slide

  18. WHAT IS HTTP, ANYWAY?
    UNDERSTANDING THE PROBLEM

    View Slide

  19. HyperText Transfer Protocol
    Defines a client and server how to communicate

    HTTP Request Methods

    HTTP Response Codes

    Safe Methods and Idempotent Methods

    View Slide

  20. HyperText Transfer Protocol
    Methods

    GET

    HEAD

    OPTIONS

    POST

    PUT

    Codes

    200

    400

    403

    404

    500

    View Slide

  21. HyperText Transfer Protocol
    Idempotent Methods

    POST

    PUT

    View Slide

  22. View Slide

  23. Your website must
    adhere to HTTP

    View Slide

  24. Andrew’s Server
    Request Methods

    GIMME

    HAVE

    Response Code

    CANHAS

    NOPE

    View Slide

  25. Your website must
    adhere to HTTP

    View Slide

  26. Goal of Software
    Solve a Problem

    Automate Behavior

    View Slide

  27. View Slide

  28. Goal of Software
    Solve a Problem

    Automate Behavior

    View Slide

  29. HTTP is the problem
    web frameworks solve
    You are the behavior
    frameworks automate

    View Slide

  30. DJANGO’S REQUEST/RESPONSE CYCLE
    UNDERSTANDING THE PROBLEM

    View Slide

  31. What is a view?
    'A view is a "type" of Web page'

    "views are for displaying content" (DORK-1)

    Dynamic generation of content

    View Slide

  32. Step 3.
    HTTP Response
    Step 1.
    HTTP Request
    Website
    Step 2.
    Computation

    View Slide

  33. Database
    Views
    URL Dispatch
    URL Patterns
    Template
    Loader
    Template
    Files
    Models
    Forms
    Context
    Processors

    View Slide

  34. Simplification
    No WSGI Server

    No View Middleware

    No Exception Handling

    View Slide

  35. Views
    URL Dispatch
    URL Patterns

    View Slide

  36. Views
    URL Patterns
    METHOD /path/to/resource/ HTTP/1.1

    View Slide

  37. Views
    URL Patterns
    METHOD /path/to/resource/ HTTP/1.1
    METHOD /path/to/resource/ HTTP/1.1

    View Slide

  38. Views
    URL Patterns
    METHOD /path/to/resource/ HTTP/1.1
    METHOD /path/to/resource/ HTTP/1.1

    View Slide

  39. What is a view?
    Accept HttpRequest object

    Generate data based on:

    HTTP request method

    Request data (POST, PUT, etc)

    Data from dispatch (from the URL path)

    Return HttpResponse object

    View Slide

  40. What is a view?
    Any Python Callable

    View Slide

  41. Why do we talk about
    functions and classes
    (and generics)?

    View Slide

  42. A BRIEF HISTORY OF VIEWS
    FROM WHEN DINOSAURS STILL ROAMED THE EARTH UNTIL THE ROBOT APOCALYPSE

    View Slide

  43. A Brief History of Views
    July 2005: Django Function views in original tarball

    July 2005: Generic views in SVN revision 304

    View Slide

  44. Functions are Rigid
    Extend generic views?

    Use classes!

    View Slide

  45. The big win from a class-based view is [...] being
    able to override parts of the behavior without
    needing to rewrite the entire view, or add 101
    parameters.
    –Alex Gaynor
    https://groups.google.com/d/msg/django-developers/J87Hm3hO9z8/gWunEs9QwMwJ

    View Slide

  46. A Brief History of Views
    July 2005: Django Function views in original tarball

    July 2005: Generic views in SVN revision 304

    March 2008: Joseph Kocherhans #6735

    View Slide

  47. Class-Based Views
    Simple (Usable - principle of least surprise)

    Extendable

    Inheritance

    Decorators

    Safe (Thread-Safe)

    Testable

    View Slide

  48. A Brief History of Views
    July 2005: Django Function views in original tarball

    July 2005: Generic views in SVN revision 304

    March 2008: Joseph Kocherhans #6735

    March 2011: (G)CBV Released in Django 1.3

    View Slide

  49. The Naming Problem
    Generic Views

    Class-Based Views (CBV)

    Class-Based + Generic Views (CBGV)

    View Slide

  50. The Naming Problem
    Object Views

    Generic (Class-Based) Views (GCBV)

    View Slide

  51. State of the Pony
    Function Views

    Object Views (Class-Based Views)

    Generic (Class-Based) Views

    View Slide

  52. FUNCTION VIEWS
    HOW I LEARNED TO STOP WORRYING AND LOVE NON-COMPLIANCE

    View Slide

  53. class ExampleModel(models.Model):
    name = models.CharField(max_length=31)
    slug = models.SlugField(max_length=31)
    def __str__(self):
    return self.name.title()

    View Slide

  54. from django.conf.urls import url
    from . import views
    urlpatterns = [
    url(r'^(?P[\w\-]+)/$',
    views.model_detail,
    name='model_detail'),
    ]

    View Slide

  55. from django.shortcuts import (
    get_object_or_404, render)
    from .models import ExampleModel
    def model_detail(request, *args, **kwargs):
    request_slug = kwargs.get('slug')
    example_obj = get_object_or_404(
    ExampleModel, slug=request_slug)
    return render(
    request,
    'viewsapp/detail.html',
    {'object': example_obj})

    View Slide

  56. Database
    Views
    URL Dispatch
    URL Patterns
    Template
    Loader
    Template
    Files
    Models
    Forms
    Context
    Processors

    View Slide

  57. from django.shortcuts import (
    get_object_or_404, render)
    from .models import ExampleModel
    def model_detail(request, *args, **kwargs):
    request_slug = kwargs.get('slug')
    example_obj = get_object_or_404(
    ExampleModel, slug=request_slug)
    return render(
    request,
    'viewsapp/detail.html',
    {'object': example_obj})

    View Slide

  58. Views
    URL Patterns
    METHOD /path/to/resource/ HTTP/1.1
    METHOD /path/to/resource/ HTTP/1.1

    View Slide

  59. from django.http import HttpResponseNotAllowed
    def model_detail(request, *args, **kwargs):
    if request.method == 'GET':
    request_slug = kwargs.get('slug')
    example_obj = get_object_or_404(
    ExampleModel, slug=request_slug)
    return render(
    request,
    'viewsapp/detail.html',
    {'object': example_obj})
    return HttpResponseNotAllowed(['GET'])

    View Slide

  60. from django.views.decorators.http import \
    require_http_methods
    @require_http_methods(['GET', 'HEAD'])
    def model_detail(request, *args, **kwargs):
    request_slug = kwargs.get('slug')
    example_obj = get_object_or_404(
    ExampleModel, slug=request_slug)
    return render(
    request,
    'viewsapp/detail.html',
    {'object': example_obj})

    View Slide

  61. @require_http_methods(['GET', 'HEAD'])
    def model_detail(request, *args, **kwargs):
    ...

    @require_safe
    def model_detail(request, *args, **kwargs):
    ...

    View Slide

  62. $ ./manage.py runserver
    $ telnet localhost 8000

    View Slide

  63. GET /django/ HTTP/1.1
    HTTP/1.0 200 OK
    Date: Wed, 26 Aug 2015 17:38:38 GMT
    Server: WSGIServer/0.2 CPython/3.4.3
    Content-Type: text/html; charset=utf-8
    X-Frame-Options: SAMEORIGIN

    View Slide

  64. OPTIONS /django/ HTTP/1.1
    HTTP/1.0 405 METHOD NOT ALLOWED
    Date: Wed, 26 Aug 2015 17:43:44 GMT
    Server: WSGIServer/0.2 CPython/3.4.3
    Allow: GET, HEAD
    Content-Type: text/html; charset=utf-8
    X-Frame-Options: SAMEORIGIN

    View Slide

  65. POST /django/ HTTP/1.1
    HTTP/1.0 403 FORBIDDEN
    Date: Wed, 26 Aug 2015 17:41:45 GMT
    Server: WSGIServer/0.2 CPython/3.4.3
    Content-Type: text/html
    X-Frame-Options: SAMEORIGIN

    View Slide

  66. url(r'^create/$',
    views.model_create,
    name='model_create'),

    View Slide

  67. def model_create(request, *args, **kwargs):
    if request.method == 'POST':
    form = ExampleForm(request.POST)
    if form.is_valid():
    new_obj = form.save()
    return redirect(new_obj)
    else:
    form = ExampleForm()
    return render(
    request,
    'viewsapp/form.html',
    {'form': form})

    View Slide

  68. View Slide

  69. @require_http_methods(['GET', 'HEAD', 'POST'])
    def model_create(request, *args, **kwargs):
    ...

    View Slide

  70. CLASS-BASED VIEWS
    AS DRY AS THE SAHARA

    View Slide

  71. url(r'^(?P[\w\-]+)/$',
    views.ModelDetail.as_view(),
    name='model_detail'),

    View Slide

  72. @require_safe
    def model_detail(request, *args, **kwargs):
    request_slug = kwargs.get('slug')
    example_obj = get_object_or_404(
    ExampleModel, slug=request_slug)
    return render(
    request,
    'viewsapp/detail.html',
    {'object': example_obj})

    View Slide

  73. def model_detail(request, *args, **kwargs):
    request_slug = kwargs.get('slug')
    example_obj = get_object_or_404(
    ExampleModel, slug=request_slug)
    return render(
    request,
    'viewsapp/detail.html',
    {'object': example_obj})

    View Slide

  74. def get(self, request, *args, **kwargs):
    request_slug = kwargs.get('slug')
    example_obj = get_object_or_404(
    ExampleModel, slug=request_slug)
    return render(
    request,
    'viewsapp/detail.html',
    {'object': example_obj})

    View Slide

  75. class ModelDetail(View):
    def get(self, request, *args, **kwargs):
    request_slug = kwargs.get('slug')
    example_obj = get_object_or_404(
    ExampleModel, slug=request_slug)
    return render(
    request,
    'viewsapp/detail.html',
    {'object': example_obj})

    View Slide

  76. OPTIONS /django/ HTTP/1.1
    HTTP/1.0 200 OK
    Date: Wed, 26 Aug 2015 17:48:24 GMT
    Server: WSGIServer/0.2 CPython/3.4.3
    Allow: GET, HEAD, OPTIONS
    X-Frame-Options: SAMEORIGIN
    Content-Type: text/html; charset=utf-8
    Content-Length: 0

    View Slide

  77. url(r'^create/$',
    views.ModelCreate.as_view(),
    name='model_create'),

    View Slide

  78. class ModelCreate(View):
    context_object_name = 'form'
    form_class = ExampleForm
    template_name = 'viewsapp/form.html'
    def get(self, request, *args, **kwargs):
    ...
    def post(self, request, *args, **kwargs):
    ...

    View Slide

  79. class ModelCreate(View):
    def get(self, request, *args, **kwargs):
    return render(
    request,
    self.template_name,
    {self.context_object_name:
    self.form_class()})

    View Slide

  80. class ModelCreate(View):
    def post(self, request, *args, **kwargs):
    bound_form = self.form_class(request.POST)
    if bound_form.is_valid():
    new_obj = bound_form.save()
    return redirect(new_obj)
    return render(
    request,
    self.template_name,
    {self.context_object_name:
    bound_form})

    View Slide

  81. @require_http_methods(['GET', 'HEAD', 'POST'])
    def model_create(request, *args, **kwargs):
    if request.method == 'POST':
    form = ExampleForm(request.POST)
    if form.is_valid():
    new_obj = form.save()
    return redirect(new_obj)
    else:
    form = ExampleForm()
    return render(
    request,
    'viewsapp/form.html',
    {'form': form})

    View Slide

  82. class ModelCreate(View):
    def get(self, request, *args, **kwargs):
    # show form
    def post(self, request, *args, **kwargs):
    # show form if error
    # use data if valid

    View Slide

  83. GENERIC CLASS-BASED VIEWS
    OH FOR THE LOVE OF GRAPH THEORY

    View Slide

  84. from django.views.generic import DetailView, View
    class ModelDetail(DetailView):
    model = ExampleModel
    template_name = 'viewsapp/detail.html'

    View Slide

  85. from django.views.generic import (
    CreateView, DetailView)
    class ModelCreate(CreateView):
    context_object_name = 'form'
    form_class = ExampleForm
    template_name = 'viewsapp/form.html'

    View Slide

  86. The Problem
    put(*args, **kwargs)

    The PUT action is also handled and just passes all
    parameters through to post().

    https://docs.djangoproject.com/en/1.8/ref/class-based-views/mixins-editing/
    #django.views.generic.edit.ProcessFormView.put

    View Slide

  87. View Slide

  88. View Slide

  89. View Slide

  90. View Slide

  91. View Slide

  92. https://ccbv.co.uk

    View Slide

  93. Stick to the basics
    display a list of objects

    display a single object

    create a single object

    update a single object

    delete a single object

    View Slide

  94. View Slide

  95. ENHANCE!
    MAKING VIEWS EASIER

    View Slide

  96. Classy Class Based Views
    http://ccbv.co.uk

    View Slide

  97. View Slide

  98. View Slide

  99. View Slide

  100. View Slide

  101. View Slide

  102. django-decorator-plus

    View Slide

  103. from decorator_plus import (
    require_form_methods, require_safe_methods)
    @require_safe_methods
    def model_detail(request, *args, **kwargs):
    ...
    @require_form_methods
    def model_create(request, *args, **kwargs):
    ...

    View Slide

  104. from decorator_plus import require_http_methods
    @require_http_methods(['GET'])
    def model_detail(request, *args, **kwargs):
    ...
    @require_http_methods(['GET', 'POST'])
    def model_create(request, *args, **kwargs):
    ...

    View Slide

  105. @require_safe_methods
    @require_http_methods(['GET'])
    @require_form_methods
    @require_http_methods(['GET', 'POST'])
    HEAD && OPTIONS
    Included

    View Slide

  106. IN CONCLUSION
    ONE SIZE FITS NO-ONE

    View Slide

  107. Answers:
    Django handles HTTP for me! Why do I have to
    worry about it?

    When is Django deprecating function views?

    Aren’t all class-based views generic?

    How do I choose what kind of view to use?

    View Slide

  108. The Questions
    Does a generic view do what I want (or almost)? If
    so, use a generic (and https://ccbv.co.uk).

    Do I need to inherit/share behavior with another
    view? If so, use a CBV.

    If not, then it doesn’t matter.

    View Slide

  109. Key Take-Aways
    The view is Django’s solution to HTTP methods

    All views are callables

    require_http_methods() should be used with
    function views

    Generic Class-Based Views (GCBV) are pre-
    programmed Class-Based Views (CBV), and are
    separate concepts with different uses

    View Slide

  110. Thank you!
    Related Materials: afrg.co/views
    Django Unleashed: afrg.com/dju
    Django Class: afrg.co/class
    @andrewsforge

    View Slide