Django

Índex

Django

  • Django

  • Instal·lació / Installation
    • Install Python
      • optionally: PyDev (Eclipse plugin)
    • Install Django
      • one of the following:
        • from linux distribution packages
          • urpmi python-django
        • easy_install:
          • easy_install --uprade django
        • pip
        • virtualenv + pip
          • system-wide
            • # virtualenv /opt/PYTHON27
              # source /opt/PYTHON27/bin/activate
          • project-based
            • cd my_project
            • virtualenv venv
            • source venv/bin/activate
          • # pip install Django
          • # pip install Django==1.7.7
          • # deactivate
      • it will be installed on:
        • /usr/lib/python2.7/site-packages/django/
        • or, if using virtualenv:
          • /path/to/your/venv/lib/python2.X/site-packages/django/
      • other modules, using virtualenv:
    • virtualenv
  • Biblioteques addicionals / Additional libraries
  • Documentació / Documentation
    • Release notes
    • Tutorial
    • Documentation (1.6) (1.5)
      • First steps
      • The model layer
      • The view layer
      • The template layer
      • Forms
      • The development process
      • The admin
      • Internationalization an location
      • Python compatibility
      • Geographic framework
        • GeoDjango
          • GeoDjango installation
            • With PostgreSQL
              • Install and setup PostGis
              • Python requirements
                • Mageia
                  • urpmi postgresql9.4-devel
                • CentOS
                  • yum install postgresql-devel
                • pip install psycopg2
              • Problemes / Problems
                • When filtering (GET), http server response is: "permission denied for relation spatial_ref_sys"
                  • Solution
                    • Connect to database as root user:
                      • Mageia
                        • psql --username postgres your_django_db
                      • CentOS
                        • sudo su - postgres
                          psql your_django_db
                    • grant permissions to your_django_user
                      • your_django_db=# GRANT SELECT ON spatial_ref_sys TO your_django_user;
          • Admin
          • Tutorial
          • Example
            • settings.py
              • DATABASES = {
                    'default': {
                        #'ENGINE': 'django.db.backends.mysql',
                        'ENGINE': 'django.contrib.gis.db.backends.postgis',
                        'NAME': 'my_db',
                        'USER': 'my_user',
                        'PASSWORD': open(os.path.join(BASE_DIR, 'db_p.txt'),'r').read().strip(),
                        'HOST': '127.0.0.1',                      # Set to empty string for localhost.
                        'PORT': '',                      # Set to empty string for default.
                    }
                }

              • INSTALLED_APPS = (
                ...
                    'django.contrib.gis',
                )

            • models.py
              • #from django.db import models
                from django.contrib.gis.db import models

                class MyModel(models.Model):
                    ...
                    # geographical coordinates
                    place = models.PointField(_("Place"), help_text=_("Place"), blank=True, null=True)
                    # to allow geo queries, even if geo fields are in a OneToOneField
                    objects = models.GeoManager()

              • #from django.db import models
                from django.contrib.gis.db import models

                class Location(models.Model):
                    """
                    Geographical location
                    """
                    name = models.CharField(_("Name"), help_text=_("Location name"), max_length=100, blank=True, null=True)
                    point = models.PointField(_("Geographical point"), help_text=_('In GeoJSON format (e.g.: {"type": "Point","coordinates": [1.1099624632241494,41.15451486130159]})'), blank=True, null=True)

                class MyModel(models.Model):
                    ...
                    # geographical coordinates
                    location = models.OneToOneField( Location, verbose_name=_("Location"), help_text=_("Location of the object"), blank=True, null=True )
                    # to allow geo queries, even if geo fields are in a OneToOneField
                    objects = models.GeoManager()

            • admin.py
              • #from django.contrib import admin
                from django.contrib.gis import admin

                admin.site.register(MyModel)
              • #from django.contrib import admin
                from django.contrib.gis import admin

                admin.site.register(Location, admin.OSMGeoAdmin)
                admin.site.register(MyModel)
            • serializers.py
              • class MyModelSerializer(serializers.ModelSerializer):
                  
                    class Meta:
                        model = MyModel
                        fields = (..., 'place',)  

              • from rest_framework_gis.serializers import GeoFeatureModelSerializer

                class LocationSerializer(GeoFeatureModelSerializer):
                    """ A class to serialize locations as GeoJSON compatible data """

                    class Meta:
                        model = Location
                        id_field = False
                        geo_field = 'point'
                        fields = ('name',)


                class MyModelSerializer(serializers.ModelSerializer):
                    location = LocationSerializer(required=False)

                    def create(self, validated_data):
                        if 'location' in validated_data:
                            # create the location object
                            location_data = validated_data.pop('location')
                            location = Location.objects.create(**location_data)
                            # relate it to the object
                            validated_data['location'] = location
                        my_model = MyModel.objects.create(**validated_data)
                        return my_model
                   
                    class Meta:
                        model = MyModel
                        fields = (..., 'location',)    

          • Filters
            • database
              distance_filter_convert_meters
              lat-lon
              WGS84
              SRID 4326
              True
              default
              meters
              SRID 3875 (900913) False
            • views.py
              • class MyModelViewSet(viewsets.ModelViewSet)
                    ...
                    # filter
                    filter_backends = (DistanceToPointFilter,)

                    # geo
                    distance_filter_field = 'my_field_containing_point'
                    # as distance in filter is specified in meters and database is in lat-lon (srid 4326), conversion is needed
                    distance_filter_convert_meters = True

          • Djangorestframework
            • djangorestframework-gis
              • Installation
                • pip install djangorestframework-gis
              • Example
                • settings.py
                  • INSTALLED_APPS = (
                        ...
                        'rest_framework',
                        'rest_framework_gis',
                    )
      • Other core functionalities
      • Middleware
    • Using Django
    • Django snippets
    • "How-to" guides
    • Trespams: Django
    • contrib packages
  • Resum
    • New project
    • New application
      • cd my-project
      • python manage.py startapp my-app
  • Estructura de directoris / Directory layout (Django 1.4 release notes):
    • djcode_1.3
      mysite
      __init__.py
      manage.py
      toto.db
      settings.py
      urls.py
      polls
      __init__.py
      models.py
      tests.py
      views.py
      urls.py



      djcode_1.4
      mysite
      manage.py
      sqlite.db
      mysite
      __init__.py
      settings.py
      urls.py
      wsgi.py
      polls
      __init__.py
      models.py
      tests.py
      views.py
      urls.py

      .project
      .pydevproject


    • project: django-admin.py startproject mysite
    • application: python manage.py startapp polls
    • Eclipse files (PyDev)
  • Creació d'un projecte / Start a project (a project contains several apps)
    • cd [~/src/]djcode
    • create the project ("mysite"):
    • configuració de les bases de dades / database setup:

      • SQLite
        • mysite/settings.py
          • DATABASES = {
            'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': '/absolute/path/to/polls.db', # absolute path to database file if using sqlite3.
            }
            }
          • INSTALLED_APPS = (
                ...
            )
      • PostgreSQL
      • MariaDB / MySQL
        • Creeu un usuari, una contrasenya i una base de dades / Create user (user_name) and password (password_for_user_name) and a database (database_name)
          • option 1: manually
            • mysql -u root -p -h localhost
              • CREATE DATABASE IF NOT EXISTS database_name;
                GRANT ALL ON database_name.* TO 'user_name'@'localhost' IDENTIFIED BY 'password_for_user_name';
                FLUSH PRIVILEGES;
                exit;
              • CREATE DATABASE IF NOT EXISTS database_name;
                GRANT ALL ON database_name.* TO 'user_name'@'%' IDENTIFIED BY 'password_for_user_name';
                FLUSH PRIVILEGES;
                exit;
          • option 2: by script
            • mariadb_create.sh
              • #!/bin/bash

                # add user and password
                usuari=user_name
                contrasenya=password_for_username
                base_dades=database_name

                mysql -u root -p <<EOF
                CREATE DATABASE ${base_dades};
                GRANT ALL ON ${base_dades}.* TO '${usuari}'@'localhost' IDENTIFIED BY '${contrasenya}';
                FLUSH PRIVILEGES;
                EOF


        • Instal·leu MySQL-python / Install MySQL-python
        • mysite/settings.py
          • DATABASES = {
            'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'database_name',
            'USER': 'user_name',
            'PASSWORD': 'password_for_user_name',
            'HOST': '', # Set to empty string for localhost.
            'PORT': '', # Set to empty string for default.
            }
            }
          • INSTALLED_APPS = (
                ...
            )
      • create the tables on the database (one table for each app in INSTALLED_APPS list)
        • Django >= 1.7
        • Django < 1.7
          • python manage.py syncdb
          • a més, si feu servir South / in addition, if you are using South:
            • python manage.py migrate --list
            • python manage.py migrate ...
      • per a esborrar la base de dades i començar de nou / to remove the database and start from scratch:
        1. ./esborra_base_dades.sh
          • #!/bin/bash

            mysql -u root -p <<EOF
            DROP DATABASE datbase_name;
            EOF

        2. ./crea_base_dades.sh
          • #!/bin/bash

            mysql -u root -p <<EOF
            CREATE DATABASE database_name;
            GRANT ALL ON database_name.* TO 'user_name'@'localhost' IDENTIFIED BY 'password_for_user_name';
            FLUSH PRIVILEGES;
            EOF

        3. python manage.py syncdb
    • administration:
      • mysite/settings.py (activated by default in Django 1.6)
        • INSTALLED_APPS = (
              ...
              'django.contrib.admin',
          )
      • mysite/urls.py (activated by default in Django 1.6)
        • from django.contrib import admin
          admin.autodiscover()

          urlpatterns = patterns('',
              ...
              url(r'^admin/', include(admin.site.urls)),
          )
      • update the database:
        • python manage.py syncdb
      • check that it is working:
      • poll application administration:
      • personalise the look and feel of the admin pages:
        • mysite/settings.py
          • TEMPLATE_DIRS = (
                    "/absolute/path/to/djcode/mytemplates/"
            )

            # from django 1.6:
            TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'mytemplates')]
        • copy the admin templates to ~/src/djcode/mytemplates/:
        • mkdir -p ~/src/djcode/mytemplates/admin
          cp /usr/lib/python2.7/site-packages/django/contrib/admin/templates/admin/base_site.html ~/src/djcode/mytemplates/admin/
          cp /usr/lib/python2.7/site-packages/django/contrib/admin/templates/admin/index.html ~/src/djcode/mytemplates/admin/
        • edit the template admin/base_site.html (títol)
        • edit the template admin/index.html (llistat d'aplicacions)
    • create the first app ("polls"):
      • cd djcode/mysite
      • python manage.py startapp polls
      • modify general files:
        • mysite/mysite/settings.py
          • INSTALLED_APPS = (
            ...
            'polls',
            )
        • mysite/mysite/urls.py
          • ...
            urlpatterns = patterns('',
                url(r'^polls/', include('polls.urls')),
                ...
            )
      • create specific files:
      • [check how the database will be created]:
        • python manage.py sql polls
        • python manage.py validate
        • python manage.py sqlcustom polls
        • python manage.py sqlclear polls
        • python manage.py sqlindexes polls
        • python manage.py sqlall polls
      • create the polls tables into the database:
        • python manage.py syncdb
      • other manage.py functions:
        • list of available functions
          • python manage.py help
        • call the API from command line:
          • if you are using virtualenv:
            • source env/bin/activate
          • python manage.py shell (sets up the environment)
            • from my_app.models import *
          • python manage.py shell <toto.py
            • toto.py
              • from my_app.models import *

                m = MyModel.objects.filter(...)
                print m
                ...
          • Alternative
            • Executing Python script from Django shell
            • standalone.py
              • import sys, os, django
                sys.path.append('/path/to/my_project')
                os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_project.settings")
                django.setup()


                # put your code here. E.g.:
                from my_app.models import *

                m = MyModel.objects.filter(...)
                print m

                ...
            • python standalone.py
            • or run standalone.py from Eclipse to debug
        • create a (or several) superuser(s):
        • browse the database:
          • python manage.py dbshell
        • validate:
          • python manage.py validate
        • inspect database:
          • python manage.py inspectdb
        • ordres pròpies / custom commands
          • Writing custom django-admin commands
            • cd myproject/my_app
            • mkdir management
            • touch __init__.py
            • mkdir commands
            • touch __init__.py
            • my_command.py
              • from django.core.management.base import BaseCommand, CommandError
                from my_app.models import MyModel

                class Command(BaseCommand):
                    help = 'This is the help text'

                    def handle(self, *args, **options):
                        self.stdout.write('Successfully done')

            • myproject/env/bin/python manage.py my_command
    • Request and response objects
    • Built-in template tags and filters
      • block, cycle (for), ...
    • How to access mod_ssl environment variables from django using mod_wsgi?
    • Javascript: window.onload
      • service/template/main.html
        • {% block extrahead %}
          <script type="text/javascript">
          function carrega() { ... }
          [...]
          {% endblock %}
        • {%block onload %}carrega(){% endblock %}

MVC

model
serialization
view
urls

file

attributes
type
class
attributes
functions
HTML templates / renderers method

from django.db import models
  • models.Model



function-based




def detail(request, poll_id)

GET
url(r'^(?P<poll_id>\d+)/$', 'detail'),








POST ...
forms.py




# non-ORM (not linked to a model)
from django import forms
  • forms.Form
  • name
  • message
generic class-based:
from django.views import generic



generic.edit.FormView
  • template_name
  • form_class
  • success_url


POST url(... MyFormView.as_view() ),
# ORM (linked to a model)
from django import forms




class Meta:
  model = MyModel
  fields = ...
  ...



generic.edit.CreateView
  • model
  • fields
  • template_name
  • template_name_suffix
  • object

mymodel_form.html POST
url(... MyModelCreateView.as_view() ),
generic.list.ListView
  • context_object_name

mymodel_list.html GET url(r'^mymodels/$', MyModelListView.as_view()),
generic.detail.DetailView


mymodel_detail.html GET url(... MyModelDetailView.as_view() ),
generic.edit.UpdateView
  • template_name_suffix
  • object

mymodel_form.html PUT,PATCH
url(... MyModelUpdateView.as_view() ),
generic.edit.DeleteView
  • template_name_suffix

mymodel_confirm_delete.html DELETE
url(... MyModelDeleteView.as_view() ),
admin.py
from django.contrib import admin
  • admin.ModelAdmin
  • admin.TabularInline
  • model
  • fields
  • fieldsets
  • readonly_fields
  • list_display
  • inlines
  • actions







url(r'^admin/', include(admin.site.urls)),
serializers.py
from rest_framework import serializers
  • serializers.Serializer
  • serializers.ModelSerializer
  • serializers.HyperlinkedModelSerializer
class Meta:
  model
  fields
  exclude
  read_only_fields
  ...
APIView:
(no need to be attached to a model)
from rest_framework.views import APIView
APIView
  • renderer_classes
  • parser_classes
  • authentication_classes
  • throttle_classes
  • permission_classes
  • content_negotiation_class

  • get_renderers()
  • get_parsers()
  • get_authenticators()
  • get_throttles()
  • get_permissions()
  • get_content_negotiator()
(must be implemented by the derived class):
  • post( request,... )
  • get( request,... )
  • put( request,... )
  • patch( request,... )
  • delete( request,... )
Renderers:
  • JSONRenderer
  • UnicodeJSONRenderer
  • JSONPRenderer
  • YAMLRenderer
  • UnicodeYAMLRenderer
  • XMLRenderer
  • TemplateHTMLRenderer (Django)
  • StaticHTMLRenderer
  • HTMLFormRenderer
  • BrowsableAPIRenderer
  • MultiPartRenderer
  • Custom renderers
  • POST
  • GET
  • PUT
  • PATCH
  • DELETE

generics:
from rest_framework import generics

generics.GenericAPIView
  • (attributes from APIView) +
  • model
  • queryset
  • serializer_class
  • lookup_field
  • lookup_url_kwarg
  • paginate_by
  • paginate_by_param
  • pagination_serializer_class
  • page_kwarg
  • filter_backends



generics.CreateAPIView
post( request,... ) POST
url(... MyModelCreateView.as_view() ),
generics.ListAPIView
get( request,... ) GET
url(r'^mymodels/$', MyModelListView.as_view() ),
generics.RetrieveAPIView
get( request,... ) GET url(... MyModelDetailView.as_view() ),
generics.UpdateAPIView

put( request,... ) PUT
url(... MyModelUpdateView.as_view() ),

patch( request,... ) PATCH
generics.DestroyAPIView
delete( request,... ) DELETE url(... MyModelDeleteView.as_view() ),
...


...

viewsets:
from rest_framework import viewsets
viewsets.GenericViewSet



from rest_framework.routers import DefaultRouter, SimpleRouter
router = SimpleRouter()
router.register(r'mymodels', views.MyModelViewSet, base_name='mymodel')
urlpatterns = patterns('',
    url(r'^', include(router.urls)),
)
viewsets.ModelViewSet

create( request,... )
POST

list( request,... ) GET

retrieve( request,... ) GET

update( request,... ) PUT

partial_update( request,... ) PATCH

delete( request,... ) DELETE
viewsets.ReadOnlyModelViewSet

list( request,... ) GET

retrieve( request,... ) GET
...




file
attributes type class
attributes
functions HTML templates / renderers
method
model serialization view urls


  • Model

    urls.py
    View


    models
    forms / serializers
    admin (predefined)
    views (custom)
    templates


    forms
    serializers
    usage


    • constrained to dealing with HTML output, and form encoded input
    • used by djangorestframework
    • not constrained to dealing with HTML output, and form encoded input




    file

    models.py
    forms.py
    serializers.py

    admin.py
    views.py
    *.html
    not using database
    (simple POST)

    -
    from django import forms

    class MyForm(forms.Form):
      foo = forms.BooleanField(required=False)
      bar = forms.IntegerField(help_text='Must be an integer.')
      baz = forms.CharField(max_length=32, help_text='Free text.  Max length 32 chars.')
    from rest_framework import serializers

    class SnippetSerializer(serializers.Serializer):
        ...



    def contacte(request):
     
    # if the form has been submitted, we need to process the form data
      if request.method=='POST':
        # create a form instance and populate it with data from the request:
       
    formulari = MyForm(request.POST) # a form bound to the POST data
       
        # check whether it is valid:
        if formulari.is_valid():
          # process the data
          ...
          return HttpResponseRedirect('/') # redirect after POST

      # if a GET (or any other method), we'll create a blank form
      else:
        formulari = MyForm() # unbound form
      return render(request, 'contacte.html',{'formulari':formulari})

    contacte.html:
    • ...
      <form action="/your-name/"method="post">
          {% csrf_token %}
          {{ formulari }}
          input type="submit" value="Submit" />
      </form>


    from djangorestframework.views import View
    from resourceexample.forms import MyForm

    class AnotherExampleView(View):
      form = MyForm

     
    def get(self, request)
      ...
      def post(self, request)
       
    return "POST request with content: %s" % (repr(self.CONTENT))

    using database
    user
    user
    django.contrib.auth.forms.UserCreationForm
    from rest_framework import serializers

    class UserSerializer (serializers.HyperlinkedModelSerializer)
        class Meta:
            model = User
            fields = ('url', 'username', 'email', 'groups')



    from django.contrib.auth.forms import UserCreationForm

    def nou_usuari(request):
      if request.method=='POST':
        formulari = UserCreationForm(request.POST)
        if formulari.is_valid:
          formulari.save()
          return HttpResponseRedirect('/')
      else:
        formulari = UserCreationForm()
      return render_to_response('nou_usuari.html',{'formulari':formulari}, context_instance=RequestContext(request))


    django.contrib.auth.forms.AuthenticationForm


    from django.contrib.auth.forms import AuthenticationForm

    def entrada(request):
      if not request.user.is_anonymous():
        return HttpResponseRedirect('/zona_privada')
      if request.method == 'POST':
        formulari = AuthenticationForm(request.POST)
        if formulari.is_valid:
            usuari = request.POST['username']
            contrasenya = request.POST['password']
            compte = authenticate(username=usuari, password=contrasenya)
            if acces is not None:
              if compte.is_active:
                login(request,compte)
                return HttpResponseRedirect('/zona_privada')
              else:
                return render_to_response('compte_no_actiu.html', context_instance=RequestContext(request))
            else:
              return render_to_response('usuari_incorrecte.html', context_instance=RequestContext(request))
      else:
          formulari = AuthenticationForm()
      return render_to_response('entrada.html',{'formulari':formulari}, context_instance=RequestContext(request))


    custom
    from django.db import models
    class MyModel(models.Model):
      titol = models.CharField(max_length=30)
      nombre_pagines = models.IntegerField()
    from django.forms import ModelForm
    class MyModelForm(ModelForm)
      class Meta:
        model = MyModel

    from django.conf.urls import patterns, url
    from polls import views

    urlpatterns = patterns('',
       # ex: /polls/
       url(r'^$', views.index, name='index'),
       # ex: /polls/5/
       url(r'^(?P<poll_id>\d+)/$', views.detail, name='detail'),
       # ex: /polls/5/results/
       url(r'^(?P<poll_id>\d+)/results/$', views.results, name='results'),
       # ex: /polls/5/vote/
       url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),
    )
    class MyModelAdmin(admin.ModelAdmin):
      form = MyModelForm

    from django.shortcuts import render, get_object_or_404
    from polls.models import Poll

    def index(request):
       latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
       context = {'latest_poll_list': latest_poll_list}
       return render(request, 'polls/index.html', context)

    def
    detail(request, poll_id):
       poll = get_object_or_404(Poll, pk=poll_id)
       return render(request, 'polls/detail.html', {'poll': poll})
    ...


    • polls/mymodel_form.html
      • <form action="..." method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="Submit" />
        </form>

    • polls/index.html
      • {% if latest_poll_list %}
           <ul>
          {% for poll in latest_poll_list %}
          <li><a href="/polls/{{poll.id }}/">{{poll.question }}</a></li>
          {% endfor %}
          </ul>
        {% else %}
          <p>No polls are available.</p>
        {% endif %}
    • polls/detail.html
      • ...
    • polls/results.html
      • ...
    • ...
    from django.conf.urls import patterns, url
    from polls import views

    urlpatterns = patterns('',
       url(r'^$', views.MyModelCreateView.as_view(), name='mymodel_create'),
       url(r'^$', views.IndexView.as_view(), name='index'),
       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<poll_id>\d+)/vote/$', views.vote, name='vote'),
    )
    Utilització de vistes genèriques / Using generic views:

    from django.views import generic
    from polls.models import Choice, Poll

    class MyModelCreateView(generic.CreateView):
      model = MyModel

    class IndexView(generic.ListView):
      # if template_name is not specified, default is:
      # <app_name>/<model_name>_list.html
      template_name = 'polls/index.html'

      # if context_object_name is not specified, default is:
      # <model_name>_list
      context_object_name = 'latest_poll_list'

      def get_queryset(self):
        """Return the last five published polls."""
        return Poll.objects.order_by('-pub_date')[:5]

    class DetailView(generic.DetailView):
      model = Poll

     
    # if template_name is not specified, default is:
      # <app_name>/<model_name>_detail.html
     
    template_name = 'polls/detail.html'

    class ResultsView(generic.DetailView):
      model = Poll

     
    # if template_name is not specified, default is:
      # <app_name>/<model_name>_detail.html
     
    template_name = 'polls/results.html'

    def vote(request, poll_id):
      ...




    from rest_framework import serializers

    class ... (serializers.ModelSerializer)






    models forms serializers urls.py admin views templates

  • ORM: Object-relational mapping (wp)
  • Models
    • Polls example models
    • Model Meta options
      • get_latest_by
      • ordering
      • verbose_name
      • verbose_name_plural
      • ...
      • exemples / examples:
        • class Toto(models.Model):
              ...
              class Meta:
                  verbose_name = _("Model toto")
                  verbose_name_plural = _("Models toto")
                  ordering = ['-start_date']
    • Manager
      • create
      • update
        • my_instance.save()
        • only specified fields:
          • my_instance.save(update_fields=[...])
      • delete
        • my_instance.delete()
    • Camps / Fields
      • Model field reference
      • Validation
        • Validators
        • validate the range of an integer:
          • def validate_byte_char(value):
                """
                Validate if a two-character value is a valid byte in hexadecimal
                """
                try:
                    int(value, 16)
                except ValueError:
                    raise ValidationError('"%s" is not a valid byte (00 .. ff)' % value)

            class MyModel(models.Model):
                my_byte = models.CharField(max_length=2, validators=[validate_byte_char])
        • validate the uniqueness of a true boolean in the table:
          • class MyModel(models.Model):
               
            is_the_only_one = models.BooleanField(default=False)

                def clean(self):       
                    from django.core.exceptions import ValidationError
                    c = MyModel.objects.filter(is_the_only_one__exact=True) 
                    if c and self.is_the_only_one:
                        raise ValidationError({'
            is_the_only_one':_("The only one is already present")})
        • validate that the sum of two fields does not exceed a maximum
          • class MyModel(models.Model):
               
            field_1 = models.IntegerField()
                field_2 = models.IntegerField()


                def clean(self):       
                    from django.core.exceptions import ValidationError
                    total = self.field_1 + self.field_2
                    if total > settings.MAXIMUM_TOTAL:
                        # non-field error
                        raise ValidationError(
            _("The sum of field_1 and field_2 cannot exceed %d" % settings.MAXIMUM_TOTAL))
      • simple (CharField, TextField, ...) ("verbose_name" es pot ometre si és el primer atribut / can be ommited if it is the first attribute)
        • class Toto(models.Model):
              """
              General description of this model/table.
              """
              title = models.CharField(_("Title"), help_text=_("Long explanation for the title for this record"), max_length=100 )
              description = models.TextField(
          verbose_name=_("Description"), help_text=_("Description of the challenge"), default="Default text for the description.")
              second_
          description = models.TextField(verbose_name=_("Second description"), blank=True)
             
              def __unicode__(self):
                  return u'%s' % (self.title)

      • URLField
        • URLField
        • Extended for rtmp (CharField must be used instead of URLField)
          • models.py
            • from django.core.validators import URLValidator

              class MyModel(models.Model):
                  rtmp_url = models.CharField( validators=[URLValidator(schemes=['http','rtmp'])], max_length=200 )

      • Choices
        • Handle choices the right way
        • Exemple / Example
          • models.py
            • class MyModel(models.Model):
                  FIELD_A_CHOICE1= 5
                  FIELD_A_CHOICE2 = 6

                  FIELD_A_CHOICE1_STR = 'fifth'
                  FIELD_A_CHOICE2_STR = 'sixth'
                 
                  FIELD_A_CHOICES = (
                                    (FIELD_A_CHOICE1, FIELD_A_CHOICE1_STR),
                                    (FIELD_A_CHOICE2, FIELD_A_CHOICE2_STR),
                  )

                  field_a = models.IntegerField( choices=FIELD_A_CHOICES )

          • get string key from integer value (e.g. from another file):
            • from my_app.models import MyModel

              key = MyModel
              .FIELD_A_CHOICE1
              string = dict(mymodel.FIELD_A_CHOICES)[key] # will return
              FIELD_A_CHOICE1_STR
          • get integer value from string key:
            • def get_choice_value(choices, data):
                  if data.isdigit():
                      return int(data)
                  else:
                      if isinstance(choices, collections.OrderedDict):
                          # choices is already an OrderedDict
                          choices_ordered_dict = choices
                      else:
                          # choices is a tuple
                          choices_ordered_dict = collections.OrderedDict(choices)
                      for key,val in choices_ordered_dict.iteritems():
                          if val==data:
                              return key
                  return None
            • get_choice_value(MyModel.FIELD_A_CHOICES, 'sixth') # will return 6
          • get a string with a list of all values, comma-separated
            • ', '.join([v for k, v in MyModel.FIELD_A_CHOICES])
        • Djangorestframework
      • DateTimeField/ DateField / TimeField
        • Omple automàticament quan es crea:
          • data = models.DateField(auto_now_add=True)
        • Omple automàticament quan s'actualitza:
          • data = models.DateField(auto_now=True)
        • Timezone
        • Data / Date
        • Date in filter
        • Date in Python
        • Django DateField default options
          • today() vs today
        • Dates in DjangoRestFramework
        • TimeField
          • models.py
            • class MyModel(models.Model):
                  duration = models.TimeField()
          • admin.py
            • from django.db import models

              @admin.register(MyModel)

              class MyModelAdmin(admin.ModelAdmin):
                 
                  formfield_overrides = {
                      models.TimeField: {'widget': None},
                  }

        • WeekdayField (django-weekday-field)
          • pypi (1.1.0)
          • bitbucket (schinckel)
          • github (elpaso)
          • utilització / usage
            • models.py
              • from weekday_field.fields import WeekdayField

                class MyModel(models.Model):
                    weekdays = WeekdayField()

            • admin.py
              • from django.forms import CheckboxSelectMultiple

                @admin.register(MyModel)
                class MyModelAdmin(admin.ModelAdmin):
                   
                    formfield_overrides = {
                        WeekdayField: {'widget': CheckboxSelectMultiple},
                    }
        • DurationField (1.8)
          • utilització / usage
            • models.py
              • import datetime

                class MyModel(models.Model):
                    duration = models.DurationField(default=datetime.timedelta(0))
            • serializers.py (i18n version of standard serializer of DurationField)
              • from django.utils.translation import ugettext_lazy as _

                # django.utils.duration.py
                def duration_string(duration):
                    """i18n version of str(timedelta)"""
                    days = duration.days
                    seconds = duration.seconds
                    microseconds = duration.microseconds

                    minutes = seconds // 60
                    seconds = seconds % 60

                    hours = minutes // 60
                    minutes = minutes % 60

                    string = u'{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds)
                    if days:
                        if days==1:
                            string = u'{} {} '.format(days,_("day")) + string
                        else:
                            string = u'{} {} '.format(days,_("days")) + string
                    if microseconds:
                        string += '.{:06d}'.format(microseconds)

                    return string


                class ModifiedDurationField(serializers.DurationField):
                    """
                    DurationField serializer which adds the i18n word 'day[s]'.
                    """
                    def to_representation(self, value):
                        return duration_string(value)

                class MyModelSerializer(serializers.ModelSerializer):
                    duration = ModifiedDurationField( read_only=True )

      • ForeignKey
      • GenericForeignKey
        • one model can be attached to several kind of parent models
        • Generic relations (The contenttypes framework)
        • How to Use Django's Generic Relations
        • How to use GenericForeignKey in Django
        • ContentType
          • Get model from content type:
            • my_content_type.model_class()
          • Get content type from model (or instance):
            • ContentType.objects.get_for_model(MyModel)
        • Serialization with djangorestframework
        • Example:
          • from django.db import models
            from django.contrib.contenttypes.fields import GenericForeignKey
            from django.contrib.contenttypes.models import ContentType

            class TaggedItem(models.Model):
                # usual fields:
                ...

                # three parts to setup a GenericForeignKey:
                content_type = models.ForeignKey(ContentType)
                object_id = models.PositiveIntegerField()
                content_object = GenericForeignKey('content_type', 'object_id')

                def __str__(self):              # __unicode__ on Python 2
                    return self.tag
          • class Bookmark(models.Model):
                """
                A bookmark consists of a URL, and 0 or more descriptive tags.
                """
                url = models.URLField()
                tags = GenericRelation(TaggedItem)

            class Note(models.Model):
                """
                A note consists of some text, and 0 or more descriptive tags.
                """
                text = models.CharField(max_length=1000)
                tags = GenericRelation(TaggedItem)
        • Admin
      • OneToOneField
      • ManyToManyField
        • Many-to-many relationships
        • type
          • direct
            • class Topping(models.Model):
                  # ...

              class Pizza(models.Model):
                  # ...
                  toppings = models.ManyToManyField(Topping)
          • through (allows extra fields for the connection; to query for these extra fields, add the same related_name to both foreign keys in the through model) (see: admin)
            • Admin
            • ManyToMany with through in DjangoRestFramework
            • Exemple / Example
              • class Person(models.Model):
                     name = models.CharField(max_length=128)
                     def __unicode__(self):
                         return self.name

                class Group(models.Model):
                     name = models.CharField(max_length=128)
                     members = models.ManyToManyField(Person, through='Membership')
                     def __unicode__(self):
                         return self.name

                class Membership(models.Model):
                     person = models.ForeignKey(Person, related_name='membership')
                     group = models.ForeignKey(Group, related_name='membership')
                     date_joined = models.DateField()
          • symmetrical
        • do something when the manytomany relation changes
        • Serializer: aniuat i filtrat / nested and filtered (ManyToMany)
        • Queries: query with a condition in ManyToMany
    • Impressió / Print
      • How do you serialize a model instance in Django?
      • Some fields as a string with newlines
        • from django.forms.models import model_to_dict

          class MyModel():
              field1 = ...
              field2 = ...
              ...
              def summary(self):
                  d = model_to_dict(self, fields=('field1','field2',) )
                  return u'\n'.join([u'{}: {}'.format(k,v) for k,v in d.iteritems()])

    • Senyals / Signals
    • Grafs / Graphs
    • Dades inicials / Initial data
    • Sites
      • The sites framework
        • activate it (Django >=1.6)
          • settings.py
            • INSTALLED_APPS = (
                  'django.contrib.sites',
              )
            • SITE_ID = 1
          • python manage.py migrate
        • Modify default value (example.com -> first.example.org, second.example.org)
          • Django Sites Framework: Initial Data Migration Location
          • steps
            1. settings.py
              • MIGRATION_MODULES = {
                    'sites': 'my_project.migrations.sites',
                }

            2. python manage.py makemigrations sites
              • will create my_project/migrations/sites/0001_initial.py
            3. python manage.py makemigrations --empty sites
              • will create my_project/migrations/sites/0002_auto_yyyymmdd_hhmm.py
            4. edit generated 0002_auto_yyyymmdd_hhmm.py
              • # -*- coding: utf-8 -*-
                from __future__ import unicode_literals

                from django.db import models, migrations

                def insert_sites(apps, schema_editor):
                    """Populate the sites model"""
                    Site = apps.get_model('sites', 'Site')
                    Site.objects.all().delete()
                   
                    Site.objects.create(domain='first.example.org', name='first_site')
                    Site.objects.create(domain='second.example.org', name='second_site')

                class Migration(migrations.Migration):

                    dependencies = [
                        ('sites', '0001_initial'),
                    ]

                    operations = [
                        migrations.RunPython(insert_sites)                 
                    ]

        • get the current site
          • with request
            • current_site = get_current_site(request)
          • without request
            • from django.contrib.sites.models import Site
              current_site = get_current_site(request)
    • Exemple: sobreescriu el mètode de desar, per a poder-hi afegir una acció prèvia (per exemple, crear un camp calculat) / Example: override the save method to add an action before it (e.g. create a calculated field)
      • models.py
        • class Toto(models.Model):
                 
              def save(self, *args, **kwargs):
                  # put some actions here:
                 
                  # then call the parent save:
                  super(Toto, self).save(*args, **kwargs)

    • Example: m2m
      • models.py
        • ..
  • Queries
    • Making queries
      • QuerySet API

        • math notation
          django < 1.11
          django >= 1.11
          Union

          qs = qs1 | qs2
          union()
          Intersection

          • qs1 & qs2
          • filter()
          • exclude()
          intersection()
          Difference
          qs1 \ qs2
          qs1.exclude(pk__in=qs2)
          difference()
          Empty

          qs = MyModel.objects.none()
        • Exemples / Examples
        • .filter()
          • Aggregation
            • Aggregate in django with duration field
            • aggregate:
              • returns a dictionary with one or more pair key/value (average, sum, custom...)
              • Exemples / Examples
                • >>> from django.db.models import Avg
                  >>> Book.objects.all().aggregate(Avg('price'))
                  {'price__avg': 34.35}

                • >>> from django.db.models import F, FloatField, Sum
                  >>> Book.objects.all().aggregate(
                  ...    price_per_page=Sum(F('price')/F('pages'), output_field=FloatField()))
                  {'price_per_page': 0.4470664529184653}

            • annotate:
              • returns a queryset, where each object has a newly created virtual field
              • Exemples / Examples
                • >>> from django.db.models import Count
                  >>> pubs = Publisher.objects.annotate(num_books=Count('book'))
                  >>> pubs
                  <QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
                  >>> pubs[0].num_books
                  73

          • Django filter the model on ManyToMany count?
            • from django.db.models import Count
              myobjects = MyModel.objects.annotate(number_related_objects=Count('related_objects')).filter(number_related_objects__gt=0)
            • myobjects = MyModel.objects.filter(related_objets__is_valid=True).distinct()
          • Tuples
            • Django filter queryset on “tuples” of values for multiple columns
            • Exemples / Examples
              • given the following records ... :
                • user: None, field1: a, field2: b, ...
                • user: None, field1: c, field2: d, ...
                • user: None, field1: e, field2: f, ...
                • user: some_user, field1: c, field2: d, ...
                • user: some_other_user, field1: c, field2: d, ...
              • ... return the union of anonymous records and some_user records, considering that intersection is defined as those records that have the same tuple field1/filed2 (if some_user defines the tuple c/d, get the entry instead of the anonymous one):
                • user: None, field1: a, field2: b, ...
                • user: None, field1: e, field2: f, ...
                • user: some_user, field1: c, field2: d, ...
              • import operator
                from django.db.models import Q

                first_qs = MyModel.objects.filter(user=some_user)
                first_qs_f1_f2 = first_qs.values_list('field1','field2',)
                intersection_taking_f1_f2 = reduce(
                    operator.or_,
                    (Q(verb=user_verb, channel=user_channel) for f1, f2 in
                first_qs_f1_f2)
                    )
                               
                second_qs = MyModel.objects.filter(user=None).exclude(
                intersection_taking_f1_f2)
                final_qs = first_qs | second_qs
        • .complex_filter()
        • .exclude()
        • __in=
        • order_by
        • F() expressions (1.7, 1.8)
          • Filters can reference fields on the model (1.7, 1.8)
        • Complex lookups with Q objects
          • from django.db.models import Q
          • Q()
          • NOT
            • ~Q(...)
          • OR
            • Q(...) | Q(...)
          • AND
            • Q(...) & Q(...)
        • GCM devices belonging to people with a given name and joining day before today minus 5 days:
          • gcm_devices = GCMDevice.objects.filter(person__in=( Person.objects.filter(name=given_value,membership__date_joined__lt=datetime.date.today()-datetime.timedelta(days=5) ) ) )
        • Dates
          • Django: Using F arguments in datetime.timedelta inside a query
          • Hybrid (filter/Python):
            • # local time:
              now = datetime.datetime.now()
              now_date = now.date()
              now_time = now.time()
              now_weekday = now.weekday()

              # Django query
              #  get all valid events
              events = Event.objects.filter(
                  start_date__lte = now_date,
                  end_date__gte = now_date,
                  weekdays__contains = now_weekday
                  )

              # refine query in Python
              #  check if an event is active now
              active_events = []
              for event in events:
                  datetime_start = datetime.datetime.combine(now_date, event.start_time)
                  duration = event.duration
                  datetime_end = datetime_start + datetime.timedelta( hours=duration.hour, minutes=duration.minute, seconds=duration.second )
                  print '%s: %s - %s' % (event.name, datetime_start, datetime_end)
                  if (datetime_start <= now) and (datetime_end >= now):
                      active_events.append(event)
              print active_events


    • one field "registration_id" from a queryset:
      • list_fields = GCMDevice.objects.values_list('registration_id',flat=True).filter(active=True)
      • devices = GCMDevice.objects.filter(active=True)
        list_fields = list(disp.registration_id for disp in devices )
    • multiple fields "registration_id", "user__username" from a queryset:
      • list_multiple_fields = GCMDevice.objects.values_list('registration_id','user__username').filter(active=True)
    • Django (web framework): How do you query with a condition on a ManyToMany model in Django?
      • add "related_name" in ForeignKeys in through model
    • How do I sort my Django Query Set against today date?
  • URLs
    • URL dispatcher (1.6)
    • Django: How to Retrieve Query String Parameters
    • Absolute path
    • reverse
      • from django.core.urlresolvers import reverse
      • simple
      • with namespace
        • my_project/my_project/urls.py
          • urlpatterns = [
              url(r'^v1/', include('my_app.urls', namespace='my_namespace')),
            ]
        • my_project/my_app/urls.py
          • urlpatterns = [
              url(r'^my_view/$', views.MyView.as_view(), name='myview' ),
            ]
        • toto.py
          • from django.urls import reverse

            url = reverse('
            my_namespace:myview')
      • with ViewSet and namespace
        • my_project/my_project/urls.py
          • urlpatterns = [
              url(r'^v1/', include('my_app.urls', namespace='my_namespace')),
            ]
        • my_project/my_app/urls.py
          • from rest_framework import routers
            from my_app import views

            my_router = routers.SimpleRouter()
            my_router.register(r'mymodels', views.MyModelViewSet
            )

            urlpatterns = [
              url(r'^', include(my_router.urls)),
            ]
        • toto.py
          • from django.urls import reverse

            url_list = reverse('
            my_namescape:mymodel-list')
            url_detail = reverse('my_namescape:mymodel-detail', kwargs={'...':'...'})
      • rest_auth
        • my_project/my_project/urls.py
          • urlpatterns = [
                # rest-auth
                url(r'^rest-auth/', include('rest_auth.urls')),
                url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
                url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login'),
            ]
        • toto.py
          • reverse('rest_password_reset_confirm')
            reverse('rest_password_reset_confirm')
            reverse('rest_login')
            reverse('rest_logout')
            reverse('rest_password_change')
            reverse('rest_register')
            reverse('rest_password_reset')
            reverse('rest_user_details')
            reverse('rest_verify_email')
            reverse('fb_login')
            reverse('tw_login')
            reverse('tw_login_no_view')
            reverse('tw_login_no_adapter')

      • admin


        • url
          parameters
          example
          AdminSite
          Index
          index

          reverse('admin:index')
          Logout
          logout


          Password change
          password_change


          Password change done
          password_change_done

          i18n Javascript
          jsi18n


          Application index page
          app_list
          app_label
          reverse('admin:app_list', kwargs={'app_label': 'myapp'})
          Redirect to object's page
          view_on_site
          content_type_id, object_id

          ModelAdmin
          Changelist
          (list  of all instances of a given model)
          {{ app_label }}_{{ model_name }}_changelist
          • reverse('admin:myapp_mymodel_changelist')
          • reverse('admin:%s_%s_changelist' % self.get_model_info(),
                                          current_app=self.admin_site.name)
          Add
          {{ app_label }}_{{ model_name }}_add

          History
          {{ app_label }}_{{ model_name }}_history object_id
          Delete
          {{ app_label }}_{{ model_name }}_delete
          object_id

          Change
          {{ app_label }}_{{ model_name }}_change
          object_id

  • View



    • my_project/my_app/
      my_project/templates/
      URL style
      HTTP method

      models.py forms.py
      urls.py views.py
      my_app/*.html
      base.html
      {prefix}/ GET
      list
      from django.db import models
      class MyModel(models.Model):
        name =
        ...

      • from my_app.views import MyModelListView
        url(r'^mymodels/$',
        MyModelListView.as_view()),
      • from my_app.views import MyModelListView
        url(r'^mymodels/$',
        MyModelListView.as_view(template_name = 'your_template.html')),
      from django.views import generic
      class MyModelListView(generic.ListView):
        # equvalent to queryset=MyModel.objects.all()
        model = MyModel

        # optional:
        # default:
      'mymodel_list'
       
      context_object_name = 'your_name_for_list'
        #
      default: 'mymodel_list.html'
        template_name = 'your_template.html'
      • mymodel_list.html (or specified by template_name)
        • object_list, mymodel_list (or specified by context_object_name)
        • {% extends "base.html" %}

          {% block content %}
              <h2>MyModels</h2>
              <ul>
                  {% for mymodel in mymodel_list %}
                      <li>{{ mymodel.name }}</li>
                  {% endfor %}
              </ul>
          {% endblock %}
        {% load staticfiles %}
      <html>
      <head>
          <title>{% block title %}{% endblock %}</title>
      </head>
      <body>
          <img src="{% static "images/sitelogo.png" %}" alt="Logo" />
          {% block content %}{% endblock %}
      </body>
      </html>

      {prefix}/{something} class Publisher(models.Model):
        name =models.CharField(max_length=30)

      class Book(models.Model):
        title = models.CharField(max_length=100)
        publisher = models.ForeignKey(Publisher)


      • from my_app.views import MyModelListView
        url(r'^books/([\w-]+)$',
        MyModelListView.as_view()),
      class PublisherBookList(ListView):

        template_name = 'books/books_by_publisher.html'

        def get_queryset(self):
          self.publisher = get_object_or_404(Publisher,name=self.args[0])
          return Book.objects.filter(publisher=self.publisher)



      {% extends "base.html" %}

      {% block content %}
         <h2>Books</h2>
         <ul>
           {% for book in book_list %}
             <li>{{ book.title }}</li>
           {% endfor %}
         </ul>
      {% endblock %}


      ...
        # optional: add entry to the context
        def get_context_data(self, **kwargs):
          # Call the base implementation first to get a context
          context = super(PublisherBookList, self).get_context_data(**kwargs)
          # Add in the publisher
          context['publicador'] = self.publisher
        return context
      {% extends "base.html" %}

      {% block content %}
         <h2>Books for publisher {{publicador.name}}</h2>
         <ul>
           {% for book in book_list %}
             <li>{{ book.title }}</li>
           {% endfor %}
         </ul>
      {% endblock %}

      POST
      create


      url(r'^$', views.MyModelCreateView.as_view(), name='mymodel_create'), from django.views import generic
      class MyModelCreateView(generic.CreateView):
        model = MyModel
      • <model_name>_form.html (or specified by template_name)
      {prefix}/{lookup}/ GET
      retrieve



      from django.views import generic
      class MyModelDetailView(generic.DetailView):
        model = MyModel


      PUT
      update






      PATCH
      partial_update





      DELETE
      destroy





    • Polls example views
    • Function-based views
      • request
      • return
        type
        return ...
        import

        "toto"


        HttpResponse("value: %s" % toto) from django.http import HttpResponse
        text/plain
        HttpResponse("toto text", mimetype="text/plain")
        application/json
        HttpResponse(json.dumps({'key':'value'}), content_type="application/json") from django.http import HttpResponse
        import json
        text/html

        tplt = loader.get_template('polls/detail.html')
        ctxt = Context({'poll_var': p})
        HttpResponse(tplt.render(ctxt))
        from django.http import HttpResponse
        render_to_response('polls/detail.html', {'poll_var': p}) from django.shortcuts import render_to_response
        render_to_response('polls/detail.html', {'poll_var': p}, context_instance=RequestContext(request))
        render( request, 'polls/detail.html', {'poll_var': p}) from django.shortcuts import render
        TemplateResponse, SimpleTemplateResponse

        application/json
        (or default renderer)
        Response(serializer.data, status=status.HTTP_...)
        djangorestframework
        text/html render( Response(...) )
        application/ms-excel

        xlwt
    • Class-based views (API: Class-based views) (see also: REST routers)
    • Problemes / Problems
    • REST views:

      View

      Views
      REST views
      REST API
      framework
      -
      Django REST framework
      Tastypie
      file
      views.py views.py api.py
      type
      ORM non-ORM ORM non-ORM ORM non-ORM
      import


      from djangorestframework.views import View
      from tastypie.resources import ModelResource

      from tastypie.resources import Resource
      from polls.models import Poll

      from resourceexample.forms import MyForm from llibres.models import Llibre
      body



      class AnotherExampleView(View):
        form = MyForm

       
      def post(self, request)
         
      return "POST request with content: %s" % (repr(self.CONTENT))
      class LlibreResource(ModelResource):
        class Meta:
          queryset = Llibre.objects.all()
          resource_name = 'recurs_llibre'
      class LlibreResource(Resource):
        uuid = fields.CharField(attribute='uuid')
       
        class Meta:
          object_class = Llibre.objects.all()
          resource_name = 'recurs_llibre'

        def detail_uri_kwargs(...)
        def get_object_list(...)
        def obj_get_list(...)
        def obj_get(...)
        def obj_create(...)
        def obj_update(...)
        def obj_delete_list(...)
        def obj_delete(...)
        def rollback(...)

      class TotoObject(object):
        def __init__(...)

         

Exemple Polls / Polls example

  • djcode/mysite
    • manage.py
    • urls.py (URLconf)
      • from django.conf.urls.defaults import patterns, include, url

        # admin
        from django.contrib import admin
        admin.autodiscover()

        urlpatterns = patterns('',
            url(r'^polls/$', 'polls.views.index'),
            url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
            url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
            url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),

            url(r'^admin/', include(admin.site.urls)),
        )
      • from django.conf.urls.defaults import patterns, include, url

        # admin
        from django.contrib import admin
        admin.autodiscover()

        urlpatterns = patterns('polls.views',
            url(r'^polls/$', 'index'),
            url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
            url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
            url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
        )

        urlpatterns += patterns('',
            url(r'^admin/', include(admin.site.urls)),
        )
      • # This also imports the include function
        from django.conf.urls.defaults import *

        # admin
        from django.contrib import admin
        admin.autodiscover()

        urlpatterns = patterns('',
            url(r'^polls/', include('polls.urls')),
            url(r'^admin/', include(admin.site.urls)),

        )
    • settings.py
      • DATABASES = {
            'default': {
                'ENGINE': 'django.db.backends.sqlite3',
                'NAME': '/absolute/path/to/polls.db',
                        ...
      • INSTALLED_APPS = (
        ...,
        'polls'
        )
    • polls.db (base de dades / database) (si va amb Apache, ha de pertànyer al grup www-data)
    • polls
      • test.py
      • Models
        • models.py (model de la base de dades)
          • __unicode__: format en omissió, per text
          • s'hi poden afegir funcions: was_published_today, ...
          • short_description: títol que apareixerà si se'l referencia a list_display (admin.py)
          • from django.db import models
            import datetime
            from django.utils import timezone

            class Poll(models.Model):
            question = models.CharField(max_length=200)
            pub_date = models.DateTimeField('date published')

            def __unicode__(self):
            return self.question

            def was_published_today(self):
            return self.pub_date.date() == datetime.date.today()
            was_published_today.short_description = 'Published today?'

            def was_published_recently(self):
            return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
            # to tune the admin display
            was_published_recently.admin_order_field = 'pub_date'
            was_published_recently.boolean = True
            was_published_recently.short_description = 'Published recently?'

            class Choice(models.Model):
            poll = models.ForeignKey(Poll)
            choice = models.CharField(max_length=200)
            votes = models.IntegerField()

            def __unicode__(self):
            return self.choice
          • shell
            • python manage.py shell
            • from polls.models import Poll, Choice
            • creació de Poll / creation of a Poll:
              • from django.utils import timezone
                p = Poll(question="What's new?", pub_date=timezone.now())
                p.save()
            • creació de Choice / creation of Choice:
              • p = Poll.objects.get(pk=1)
                p.choice_set.create(choice_text='Not much', votes=0)
                p.choice_set.create(choice_text='The sky', votes=0)
        • admin.py (gestió de la base de dades)
          • fields: ordre dels camps / field sorting
          • admin.site.register: fa que surti a la pàgina principal / make it appears at the main page
          • fieldsets: agrupacions de camps / groups of fields
          • inlines: fa que surti a dins (relacionat via foreign key), en lloc del principi de tot / embedded instead of at the main page
            • admin.StackedInline: llista / list
            • admin.TabularInline: taula, més compacta que la llista / table, more compact than list
          • list_display: selecció de camps que cal presentar a "Select poll to change" (si no, només mostrarà els especificats per __unicode__)
          • filtres / filters:
            • list_filter
          • cerques / searches:
            • search_fields
          • navegació per dates / navigation by date:
          • from polls.models import Poll
            from django.contrib import admin

            admin.site.register(Poll)
          • from polls.models import Poll
            from django.contrib import admin

            class PollAdmin(admin.ModelAdmin):
                model = Poll
            admin.site.register(Poll, PollAdmin)
          • from polls.models import Poll
            from django.contrib import admin

            class PollAdmin(admin.ModelAdmin):
                fields = ['pub_date', 'question']
            admin.site.register(Poll, PollAdmin)
          • from polls.models import Poll
            from django.contrib import admin

            class PollAdmin(admin.ModelAdmin):
                fieldsets = [
                    (None, {'fields': ['question']}),
                    ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
                 ]
            admin.site.register(Poll, PollAdmin)
          • from polls.models import Poll
            from polls.models import Choice
            from django.contrib import admin

            class ChoiceInline(admin.StackedInline):
                model = Choice
                extra = 3

            class PollAdmin(admin.ModelAdmin):
                fieldsets = [
                    (None,               {'fields': ['question']
            , 'classes': ['wide']}),
                    ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
                ]
                inlines = [ChoiceInline]

                list_display = ('question', 'pub_date', 'was_published_today')
                list_filter = ['pub_date']
                search_fields = ['question']
                date_hierarchy = 'pub_date'

            admin.site.register(Poll, PollAdmin)
      • Views
        • urls.py (quina "view" cal presentar quan es rep una petició amb aquell patró d'url) (aquest és el contingut del fitxer quan el  fitxer urls.py del pare inclou include('polls.urls') )
          • opcionalment se li pot donar un nom (, name='etiqueta'), per a referir-s'hi després des de views.py, amb, per exemple: [reverse('etiqueta', kwargs={'num':num}) for num in range(3)]
          • es fan servir expressions regulars / regular expressions are used
          • from django.conf.urls.defaults import patterns, include, url

            urlpatterns = patterns('polls.views',
                url(r'^$', 'index'),
                url(r'^(?P<poll_id>\d+)/$', 'detail'),
                url(r'^(?P<poll_id>\d+)/results/$', 'results'),
                url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
            )
        • views.py (com es presenten els resultats)
          • Text simple (text/plain):
            • from django.http import HttpResponse

              def index(request):
                  return HttpResponse("Hello, world. You're at the poll index.")

              def detail(request, poll_id):
                  return HttpResponse("You're looking at poll %s." % poll_id)

              def results(request, poll_id):
                  return HttpResponse("You're looking at the results of poll %s." % poll_id)

              def vote(request, poll_id):
                  return HttpResponse("You're voting on poll %s." % poll_id)
          • Text simple (text/plain) a partir de base de dades:
            • from polls.models import Poll
              from django.http import HttpResponse

              def index(request):
                  latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
                  output = ', '.join([p.question for p in latest_poll_list])
                  return HttpResponse(output)
            • from polls.models import Poll,Choice
              from django.http import HttpResponse

              def detail(request, poll_id):
                  p =
              Poll.objects.get(pk=poll_id)
                  output = m.choice_set.all()
                  return HttpResponse(output)
          • Text formatat (text/html) a partir de la base de dades, segons una plantilla HTML
            • from django.template import Context, loader
              from polls.models import Poll
              from django.http import HttpResponse

              def index(request):
                  latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
                  t = loader.get_template('polls/index.html')
                  c = Context({
                      'latest_poll_list': latest_poll_list,
                  })
                  return HttpResponse(t.render(c))
            • from django.shortcuts import render_to_response
              from polls.models import Poll

              def index(request):
                  latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
                  return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})
            • ...
              from django.http import Http404

              def detail(request, poll_id):
                  try:
                      p = Poll.objects.get(pk=poll_id)
                  except Poll.DoesNotExist:
                      raise Http404
                  return render_to_response('polls/detail.html', {'poll': p})
            • ...
              from django.shortcuts import render_to_response, get_object_or_404
              from django.template import RequestContext

              def detail(request, poll_id):
                  p = get_object_or_404(Poll, pk=poll_id)
                  return render_to_response('polls/detail.html', {'poll_var': p}, context_instance=RequestContext(request))
            • ...
              from django.http import HttpResponseRedirect
              from django.core.urlresolvers import reverse

              def vote(request, poll_id):
                  p = get_object_or_404(Poll, pk=poll_id)
                  try:
                      selected_choice = p.choice_set.get(pk=request.POST['choice'])
                  except (KeyError, Choice.DoesNotExist):
                      # Redisplay the poll voting form.
                      return render_to_response('polls/detail.html', {
                          'poll': p,
                          'error_message': "You didn't select a choice.",
                      }, context_instance=RequestContext(request))
                  else:
                      selected_choice.votes += 1
                      selected_choice.save()
                      # Always return an HttpResponseRedirect after successfully dealing
                      # with POST data. This prevents data from being posted twice if a
                      # user hits the Back button.
                      return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,)))
            • def results(request, poll_id):
                  p = get_object_or_404(Poll, pk=poll_id)
                  return render_to_response('polls/results.html', {'poll': p})
          • Error views (404, 500, ...)
            • variables in URLconf (urls.py): handler404, handler500
            • files: 404.html, 500.html
        • templates (Template guide) (plantilles html per a presentar els resultats / templates for showing the results) )
          • index.html
            • {% if latest_poll_list %}
                  <ul>
                  {% for poll in latest_poll_list %}
                      <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
                  {% endfor %}
                  </ul>
              {% else %}
                  <p>No polls are available.</p>
              {% endif %}


          • detail.html
            • <h1>{{ poll_var.question }}</h1>
              <ul>
              {% for choice in poll_var.choice_set.all %}
              <li>{{ choice.choice }}</li>
              {% endfor %}
              </ul>
            • <h1>{{ poll.question }}</h1>
              {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
              <form action="/polls/{{ poll.id }}/vote/" method="post">
              {% csrf_token %}
              {% for choice in poll.choice_set.all %}
              <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
              <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
              {% endfor %}
              <input type="submit" value="Vote" />
              </form>
          • results.html
            • ...

Fitxers estàtics / Static files

  • Documentació / Documentation
  • Static files (css, js, ...) (different from media files) (Note) (es pot obtenir des de: / can be obtained from: HTML templates)
    1. general config
      • mysite/mysite/settings.py
        • # settings for django.contrib.staticfiles

          # only for production, destination of files collected by collectstatic script:

          STATIC_ROOT = '/home/user/src/djcode/mysite/collected_static/'
          STATIC_ROOT = os.path.join(BASE_DIR, 'collected_static')

          # used for creation of urls pointing to static content:
          STATIC_URL = '/static/'

          # in addition to static files from apps (inside myapp/static),
          # collect the static files in:
          STATICFILES_DIRS = (
            os.path.join(BASE_DIR, "static_general"),

          )
        • INSTALLED_APPS = (
          ...
          'django.contrib.staticfiles',
          )
    2. put the static files in their place:
      • general (per site) static files:
        • put them on the directory specified by STATICFILES_DIR: /home/user/src/djcode_1.4/mysite/mysite/static_general/
      • app-specific static files:
        • 1.6
          • mysite/polls/static/polls/css/style.css
        • old versions: put them on the "static" dir inside the app:
          • mysite/polls/static/css/polls.css
    3. Only for production / integration with Apache:
      1. copy them to STATIC_ROOT (in flat structure), specified in settings.py, by calling the script:
        • python manage.py collectstatic
        • it will take all the static content in every app (doesn't need to be specified on STATICFILES_DIRS) and all other general static files under STATICFILES_DIRS
      2. add the static directory (STATIC_ROOT) (no need to be under djcode_1.4) to Apache config file (Alias must match the STATIC_URL definition):
        IMPORTANT: comproveu els permisos d'usuari UNIX del directori / check the UNIX user permissions of the directory
      3. Alias /static/ /home/user/src/djcode_1.4/mysite/collected_static/
        <Directory /home/user/src/djcode_1.4/mysite/
        collected_static/>
          # Apache 1.2:
          #Order deny,allow
          #Allow from all
         
          # Apache 1.4:
          Require all granted
        </Directory>
    4. reference to static file
      1. mysite/mysite/settings.py
        • TEMPLATE_CONTEXT_PROCESSORS = (
            'django.core.context_processors.debug',
            'django.core.context_processors.i18n',
            'django.core.context_processors.media',
            'django.core.context_processors.static',
            'django.contrib.auth.context_processors.auth',
            'django.contrib.messages.context_processors.messages',
          )
      2. mysite/myapp/views.py (only needed for function-based views?)
        • from django.template import RequestContext
          ...
          return render_to_response('toto.html', context_instance=RequestContext(request))
      3. from templates
        • 1.6: mysite/templates/myapp/toto.html
          • {% load staticfiles %}

            <link rel="stylesheet" type="text/css" href="{% static 'polls/css/style.css' %}" />
        • old versions: mysite/myapp/templates/toto.html
          • <img src="{{ STATIC_URL }}images/hi.jpg" alt="Hi!" />
      4. from other css
        • 1.6: mysite/polls/static/polls/css/style.css
          • body {
                # image is in polls/static/polls/images/background.gif
                background: white url("images/background.gif") no-repeat right bottom;
            }

Media

  • Imatges / Images
  • Vídeo / Video
  • Managing files
  • settings.py
    • # settings for file uploads

      # absolute dir place to upload files (will be completed with directory specified by "upload_to")
      # only needed in production
      # parent dir (belonging to
      my_username) must have the right permissions:
      #   sudo usermod -a -G www-data my_username
      #   sudo chgrp www-data -R <MEDIA_ROOT>
      #   sudo chmod -R g+w
      <MEDIA_ROOT>
      #
         sudo chmod -R g+s <MEDIA_ROOT>
      MEDIA_ROOT = PROJECT_PATH + '/media/'
      MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

      #
      used for creation of urls pointing to media content:
      MEDIA_URL = '/mm/'
      #MEDIA_URL = 'http://other_server/mm/'
  • Puja fitxers / Upload files
    • File Uploads
    • Pujar un fitxer des d'un botó a l'admin
    • a un directori amb un nom ('im') fix / into a fixed name ('im') directory (uploaded files will be stored in MEDIA_ROOT/im/):
      • class Imatge(models.Model):
            nom = models.CharField(max_length=100)
            media = models.ImageField(upload_to='im')


    • a un directori amb un directori i un nom creats dinàmicament / into a dynamically created directory and name (e.g. directory based on 'im' and user name; and file name created as uuid):
      • import os

        def media_filename(instance, filename):
            ext = filename.split('.')[-1]
            new_filename = "%s.%s" % (uuid.uuid4(), ext)
            return os.path.join('im', instance.user.username, new_filename)

        class Imatge(models.Model):
            nom = models.CharField(max_length=100)   
            user = models.ForeignKey( UploadUser )
            media = models.ImageField(upload_to=media_filename)
    • Problemes / Problems
  • Serving files uploaded by a user
    • Desenvolupament / Development
      • urls.py
        • from django.conf import settings
          from django.conf.urls.static import static

          urlpatterns = patterns('',
          ...
          )
          if settings.DEBUG:
              urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    • Producció / Production
  • Sobreescriu els fitxers, vídeo o imatges pujats / Overwrite upload files, video or images
    • Bug 4339 / 11663 (comment #19)
      • models.py
        • from django.core.files.storage import FileSystemStorage

          class OverwriteStorage(FileSystemStorage):
              def _save(self, name, content):
                  if self.exists(name):
                      self.delete(name)
                  return super(OverwriteStorage, self)._save(name, content)
             
              # to avoid adding underscores for existing files
              def get_available_name(self, name):
                  return name
             
              # to avoid replacement of spaces with underscores
              def get_valid_name(self, name):
                  return name

        • class Toto(models.Model)
              toto_file = models.FileField(upload_to="toto_dir", storage=OverwriteStorage(), blank=True)
    • Overwrite File Storage System
    • ImageField overwrite image file
  • Resum / Summary:
    type
    destination path
    storage
    access
    local

    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    • default (settings.py):
    • occasional (models.py):
      • ... = models.FileField( ... storage=OverwriteStorage() ...)
    MEDIA_URL = '/mm/'
    remote
    AWS DEFAULT_S3_PATH = "media"
    MEDIA_ROOT = '/%s/' % DEFAULT_S3_PATH
    • DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
    • DEFAULT_FILE_STORAGE = 'myapp.s3utils.MediaRootS3BotoStorage'
    DEFAULT_S3_PATH = "media"
    CLOUDFRONT_DOMAIN = 'domain.cloudfront.net'
    MEDIA_URL = 'http://%s/%s/' % (CLOUDFRONT_DOMAIN, DEFAULT_S3_PATH)

Vídeo / Video

Imatges / Images

  • Projectes relacionats / Related projects:
    • django-photologue
    • django-imagekit
    • django-imaging (Ajax driven gallery field for django admin): not working for ManyToMany
    • django-stdimage
      • Features
      • Rotate from EXIF
        • Rotate variations or keep exif info for JPEG #66
          • from stdimage.utils import render_variations
            from django.core.files.base import ContentFile
            from PIL import Image
            from io import BytesIO

            def resize_and_autorotate(file_name, variations, storage):
                # https://github.com/codingjoe/django-stdimage/issues/66
                with storage.open(file_name) as f:
                    with Image.open(f) as image:
                        file_format = image.format
                        
                        if hasattr(image, '_getexif'): # only present in JPEGs
                            exif = image._getexif()
                
                            # if image has exif data about orientation, let's rotate it
                            orientation_key = 274 # cf ExifTags
                            if exif and orientation_key in exif:
                                orientation = exif[orientation_key]
                                print "[process_image]   EXIF orientation: %s" % (orientation)
                                
                                rotate_values = {
                                    3: Image.ROTATE_180,
                                    6: Image.ROTATE_270,
                                    8: Image.ROTATE_90
                                }
                
                                if orientation in rotate_values:
                                    image = image.transpose(rotate_values[orientation])
                
                            with BytesIO() as file_buffer:
                                image.save(file_buffer, file_format)
                                f = ContentFile(file_buffer.getvalue())
                                storage.delete(file_name)
                                storage.save(file_name, f)

                # render stdimage variations
                render_variations(file_name, variations, replace=True, storage=storage)

                return False  # prevent default rendering
          • Problemes / Problems
      • Problemes / Problems
        • manage.py rendervariations ...
          • si no s'especifica storage dins de StdImageField, quan agafa el valor per defecte especificat a / if not specified inside StdImageField, when it takes the default value specified in settings.py DEFAULT_FILE_STORAGE = 'my_app.s3utils.NewMediaS3BotoStorage':
            • ImportError: Module "django.core.files.storage" does not define a "NewMediaS3BotoStorage" attribute/class
            • Solució / Solution
              • myfield = StdImageField( ..., storage=... )
          • no fa cas de bucket ni location especificats dins de / it does not take into account bucket and location specified in storage=NewMediaS3BotoStorage(bucket=..., location=...)
            • File does not exist
            • Solució / Solution
              • el bucket s'ha d'especificar a / bucket must be specified in settings.AWS_STORAGE_BUCKET_NAME
              • el location s'ha d'especificar a / location must be specified in my_app/s3utils.py:
                • @deconstructible
                  class NewMediaS3BotoStorage(S3BotoStorage):
                      location=settings.S3_MEDIA_PATH

      • Example with AWS S3 (storages), Celery, djangorestframework
        • myproject/settings.py
          • INSTALLED_APPS = (
                ...
                'stdimage',
                ...
            )

          • AWS_STORAGE_BUCKET_NAME = 'my-bucket'
            AWS_AUTO_CREATE_BUCKET = True
            # media (uploaded files)
            S3_MEDIA_PATH = "media"
            MEDIA_ROOT = '/%s/' % S3_MEDIA_PATH

        • my_app/s3utils.py
          • from storages.backends.s3boto import S3BotoStorage
            from django.conf import settings
            from django.utils.deconstruct import deconstructible

            @deconstructible
            class NewMediaS3BotoStorage(S3BotoStorage):
                """
                Deconstructible subclass to avoid Django 1.7 'manage.py makemigrations' error:
                ValueError: Cannot serialize: <storages.backends.s3boto.S3BotoStorage object...
                """
                location=settings.S3_MEDIA_PATH

        • my_app/tasks.py
          • from celery import shared_task
            from stdimage.utils import render_variations

            @shared_task
            def process_image(file_name, variations, storage):
                print "[process_image] file_name=%s, variations=%s, storage=%s" % (file_name, variations, storage)
                # optionally: rotate from EXIF
                render_variations(file_name, variations, replace=True, storage=storage)

        • my_app/models.py
          • from my_app.tasks import process_image
            from stdimage.models import StdImageField

            def image_processor(file_name, variations, storage):
                process_image.delay(file_name, variations, storage)
                return False  # prevent default rendering

            class MyModel(models.Model)
                cover = StdImageField(_("Cover"), help_text=_("Cover image"),
                                      storage=NewMediaS3BotoStorage(),
                                      upload_to='covers',
                                      variations={'large': (600, 400),
                                                  'thumbnail': (100, 100, True),},
                                      render_variations=image_processor,
                                      blank=True, null=True)

        • my_app/serializers.py
          • from django.forms import ImageField as DjangoImageField
            from rest_framework import serializers

            class VariationImageField(serializers.ImageField):
                """
                Serializer for StdImageField, with specified variation.
                """
                def __init__(self, *args, **kwargs):
                    self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField)
                    self.variation = kwargs.pop('variation','')
                    super(VariationImageField, self).__init__(*args, **kwargs)

                def to_internal_value(self, data):
                    # override to deal with "null" data
                    if data=='null':
                        # TODO: if the file existed, really delete it from storage
                        return None
                    else:
                        file_object = super(fields.ImageField, self).to_internal_value(data)
                        django_field = self._DjangoImageField()
                        django_field.error_messages = self.error_messages
                        django_field.to_python(file_object)
                        return file_object

                def to_representation(self, value):
                    use_url = True

                    if not value:
                        return None

                    if use_url:
                        if not getattr(value, 'url', None):
                            # If the file has not been saved it may not have a URL.
                            return None
                        # if exists, take the variation
                        image = getattr(value, self.variation, value)
                        url = image.url
                        request = self.context.get('request', None)
                        if request is not None:
                            return request.build_absolute_uri(url)
                        return url
                    return value.name

            class MyModelSerializer(serializers.ModelSerializer):
                cover = VariationImageField( variation='thumbnail' )

    • sorl-thumbnail
  • Requisits / Requirements
  • Utilització / Usage
    • models.py
      • class UploadImatge(models.Model)
         
        media = models.ImageField(upload_to='ims')
    • camins / paths
      • relative (does not include MEDIA_ROOT):
        • imatge.media.name
      • absolute (includes MEDIA_ROOT, if used storage is FileSystemStorage or derived; when using S3BotoStorage, it will be the same as imatge.media.name):
        • imatge.media.file.name
  • Visualitza imatges / Display images:
    • AdminImageWidget
    • ManyToMany
    • Option 1: define your own image_thumb field and use it with ForeignKey (admin of Image: works; inline admin of Mosaic: it works)(sí que es veu la imatge en petit a l'inline; però una ImatgeDeFilm està lligada a un sol film i no es pot reutilitzar) en lloc de ManyToMany (no es veu la imatge a l'inline, perquè és un seleccionable simple):
      • models.py
        • class Film(models.Model):
              titol = models.CharField(max_length=100)
             
          class ImatgeDeFilm(models.Model):
              nom = models.CharField(max_length=100)
              media = models.ImageField(upload_to='ims')
              film = models.ForeignKey(Film)
             
              def image_thumb(self):
                  return u'<img src="%s" width="100" height="100"/>' % self.media.url
              image_thumb.short_description = 'Imatge en petitet'
              image_thumb.allow_tags = True

              def __unicode__(self):
                  return "%s (%s)" % (self.nom, self.media)

      • admin.py
        • class ImatgeDeFilmInline(admin.TabularInline):
              model = ImatgeDeFilm
              readonly_fields = ['image_thumb']
             
          class FilmAdmin(admin.ModelAdmin):
              inlines = [ImatgeDeFilmInline]
          admin.site.register(Film, FilmAdmin)
    • Option 2: define your own image_thumb field and use it with ManyToMany (admin of Image: it works; inline admin of Mosaic: it does not work)
      • models.py
        • class Imatge(models.Model):
              nom = models.CharField(max_length=100)
              media = models.
          ImageField(upload_to='ims')
             
              def __unicode__(self):
                  return "%s (%s)" % (self.nom, self.media)
             
              # to allow display of images in admin
              def image_thumb(self):
                  return u'<img src="%s" width="100" height="100"/>' % self.media.url
              image_thumb.short_description = 'Imatge en petitet'
              image_thumb.allow_tags = True
        • class Mosaic(models.Model):
              titol = models.CharField(max_length=100)
              imatges = models.ManyToManyField(Imatge, related_name='mosaics')
      • admin.py
        • class ImatgeAdmin(admin.ModelAdmin):
              # to display image thumbnail in the list:
              list_display = ('nom','image_thumb',)
              # to display image thumbnail on adding an image (inline or not)
              readonly_fields = ['image_thumb']
          admin.site.register(Imatge, ImatgeAdmin)
        • not inline in mosaic (simple select multiple):
          • class MosaicAdmin(admin.ModelAdmin):
                fields = ('titol', 'imatges')
            admin.site.register(Mosaic, MosaicAdmin)

        • not inline in mosaic (better select multiple in two horizontal columns: "available", "selected")
          • class MosaicAdmin(admin.ModelAdmin):
                fields = ('titol','imatges')
                filter_horizontal = ['imatges',]
            admin.site.register(Mosaic, MosaicAdmin)

        • inline images in mosaic (Working with many-to-many models):
          • class MosaicImatgeInline(admin.TabularInline):
                model = Mosaic.imatges.through
            class MosaicAdmin(admin.ModelAdmin):
                exclude = ('titol', )
                inlines = [MosaicImatgeInline,]
            admin.site.register(Mosaic, MosaicAdmin)
    • Option 3: use AdminImageWidget and ForeignKey
    • Option 4: use AdminImageWidget and ManyToMany (Image and video previews in admin inline forms for ManyToMany relation)
  • Generació d'imatges / Image generation:
      • SimpleUploadedFile

Generació al vol de camps / On the fly field generation

  • models
    • do something before the object is saved:
      • class Toto(models.Model):
            ...
            def save(self, *args, **kwargs):
                # do something only when the entry is created
                if not self.pk:
                    ...

                # do something every time the entry is updated
                ...

                # then call the parent save:
                super(Toto, self).save(*args, **kwargs)
    • do something when the manytomany relation changes:
      • from django.db.models.signals import m2m_changed

        class Toto(models.Model):
            toto_fills = models.ManyToManyField(TotoFill)
            def do_something(self):
                ...

        def totofills_changed(sender, instance, **kwargs):
            instance.do_something()
            instance.save()

        m2m_changed.connect(totbills_changed, sender=Toto.toto_fills.through)
    • How to know, before saving, if a field has changed
      • django - comparing old and new field value before saving
      • FieldTracker (django-model-utils)
        • models.py
          • from model_utils import FieldTracker

            class MyModel():
                field1 = ...
                ...
                # to track changes in fields
                tracker = FieldTracker()

                def save(self, *args, **kwargs):
                    if self.tracker.has_changed('field1'):
                        # old_value = self.tracker.previous('field1')
                        # new_value = self.field1         
                    ...

    • Create other object when an object is saved (using signals)
      • django how do i send a post_save signal when updating a user?
      • User profiles
      • myfirstapp/models.py
        • from django.db.models.signals import post_save
          from django.dispatch import receiver
          from django.db import transaction

          class MyFirstModel(models.Model):
              ...

          @receiver(post_save, sender=
          MyFirstModel)
          def create_mysecondmodel_object(sender, instance, **kwargs):

              # create
          MySecondModel
              from mysecondapp.models import MySecondModel
              try:
                  with transaction.atomic():
                      my_second_object = MySecondModel.objects.get_or_create(myfield=instance)
              except Exception as e:
                  print e

      • mysecondapp/models.py
        • from myfirstapp.models import MyFirstModel

          class MySecondModel(models.Model):
              myfield = models.OneToOneField(MyFirstModel)

  • views
  • admin
    • automatically create a list_display field:
      • def binary_status(obj):
            return '{:08b}'.format(obj.status)
        binary_status.short_description = _("Binary status")

        class TotoAdmin(admin.ModelAdmin):
            ...
            list_display =  (..., binary_status,...)
            ...
        admin.site.register(Toto, TotoAdmin)
    • URL in list_display:
      • class TotoAdmin(admin.ModelAdmin):
            list_display =  (...,'totofield_url', ...)
           
            # hyperlink to totofield in display_list
            def totofield_url(self,item):
                target = getattr(item, 'totofield')
                return '<a href="../%s/%d/">%s</a>' % (target._meta.module_name, target.id, unicode(target))
            totofield_url.allow_tags = True
            totofield_url.short_description = _("Toto field")

        admin.site.register(Toto, TotoAdmin)
    • image thumbnail in list_display:
      • my_project/settings.py
      • my_app/models.py
        • class Gallery(models.Model):
              name = models.CharField(max_length=256)
              image = models.ImageField(upload_to='im')

      • my_app/admin.py
        • @admin.register(Gallery)
          class GalleryAdmin(admin.ModelAdmin):
              list_display = ('name','image_thumbnail',)
             
              def image_thumbnail(self, item):
                  return u'<img src="%s" height="50"/>' % (item.image.url)
              image_thumbnail.short_description = 'Thumbnail'
              image_thumbnail.allow_tags = True
      • servidor de mèdia / media server
    • custom fields that are not part of the model:
      • django admin - add custom form fields that are not part of the model
      • admin auto populate user field from request
      • django prepopulate modelform - nothing happens
      • example:
        • models.py
          • class MyModel(models.Model):
                first_byte = models.IntegerField()
                second_byte = models.IntegerField()
        • admin.py
          • from django import forms

            class MyModelForm(forms.ModelForm):
                WORD_16BIT_CHOICES = (
                                       (0x0000, "0x00-0x00: code for first option"),
                                       (0x0001, "0x00-0x01
            : code for second option"),
                                       (0x0102, "0x01-0x02
            : code for third option"),
                                       (0x0201, "0x02-0x01
            : code for fourth option"),
                                       (0xffff, "0xff-0xff
            : code for last option"),
                                       )
               
                # new field (int from choices) not present in the model
                word_16bit = forms.TypedChoiceField(label=_("16-bit word"), choices=WORD_16BIT_CHOICES, required=False, coerce=int)
               
                def __init__(self, *args, **kwargs):
                    super(MyModelForm, self).__init__(*args, **kwargs)
                   
                    # if an instance already exists, get the initial value from it
                    if kwargs.has_key('instance'):
                        self.initial['word_16bit'] = self.instance.first_byte*256 + self.instance.second_byte
               
                def save(self, commit=True):
                    # calculate both bytes from the 16-bit word
                    word_16bit = self.cleaned_data.get('word_16bit', 0)
                    self.instance.first_byte = word_16bit // 256
                    self.instance.second_byte = word_16bit % 256

                    return super(MyModelForm, self).save(commit=commit)

                class Meta:
                    model = MyModel


            @admin.register(MyModel)
            class MyModelAdmin(admin.ModelAdmin):
                model = MyModel
               
                form = MyModelForm
                fields = ('word_16bit',)


    • force save of unchanged inlines
    • do something when ForeginKey related fields change:
      • Get access to ForeignKey objects at parent save in Django
      • ModelAdmin.save_related
      • models.py
        • class Toto(models.Model):
              ...
              def do_something(self):
                  ...

      • admin.py
        • class TotoAdmin(admin.ModelAdmin):
              ...
             
              def save_related(self, request, form, formsets, change):
                  # save all related data (inlines)
                  super(TotoAdmin, self).save_related(request, form, formsets, change)

                  # do something
                  form.instance.do_something()
                 
                  # if something of parent model got modified in do_something,
                  # save the parent model again
                  super(TotoAdmin, self).save_model(request, form.instance, form, change)
      • Exemple: imatge composta creada automàticament quan es desen les imatges relacionades per ForeignKey (vegeu també conversió d'imatge amb Celery) / Example: automatically created composed image from ForeignKey related images (see also image conversion with Celery):
        • models.py
          • from django.conf import settings
            from PIL import Image

            class Toto(models.Model):
                ...
                composed_image = models.ImageField(upload_to='images', verbose_name=_("Composed image"), help_text=_("Composed image"), storage=OverwriteStorage(), blank=True)
               
                def image_composition(self):
                    if self.images.count() == 0:
                        return
                   
                    NUMBER_OF_IMAGES=self.images.count()
                          
                    # recupera les mides de la primera de les imatges /
                    # get the size from the first image
                    image = self.images.all()[0]
                    OUT_WIDTH = image.media.width
                    OUT_HEIGHT = image.media.height

                   
            # mida de la imatge que cal creat / size of the image to create
                    size = (OUT_WIDTH*NUMBER_OF_IMAGES,OUT_HEIGHT)

                    # crea la imatge / create the image
                    out_image = Image.new('RGB', size)
                   
                    total_name = "%d" % (self.pk)
                    left = 0
                    right = OUT_WIDTH
                    upper = 0
                    lower = OUT_HEIGHT

                    for image in self.images.all():
                        print image.media.file.name
                       
                        in_image = Image.open(image.media.file.name)
                       
                        position = (left, upper, right, lower)
                        print "  image: paste to (%d,%d,%d,%d)" % (left, upper, right, lower)
                        out_image.paste(in_image, position )
                        left = left + OUT_WIDTH
                        right = right + OUT_WIDTH

                    image_filename = 'images/' + total_name + '_composed.png'
                    absolute_image_filename = settings.MEDIA_ROOT + image_filename
                    log.info( "saving generated image %s" % absolute_image_filename )
                    out_image.save(absolute_image_filename,'png')
                   
                    # actualitza el nom de la imatge composta /
                    # update the name of the name containing the composed image
                    self.composed_image.name = image_filename


            class ImageOfToto(models.Model):
                media = models.ImageField(upload_to='images', verbose_name=_("Media"), help_text=_("File or URL to be uploaded"), storage=OverwriteStorage())
                toto = models.ForeignKey(Toto, related_name="images")

                def image_thumb(self):
                    return u'<img src="%s" width="80" height="44"/>' % self.media.url
                image_thumb.short_description = _("Thumbnail")
                image_thumb.allow_tags = True
               
                def __unicode__(self):
                    return u'%s' % (self.media)

        • admin.py
          • class TotoAdmin(admin.ModelAdmin):

                # genera automàticament les imatges compostes /
                # automatically generate the composed image
                def save_related(self, request, form, formsets, change):
                   
                    # desa les imatges relacionades / save related images
                    super(TotoAdmin, self).save_related(request, form, formsets, change)

                    # genera la imatge composta / generate the composed image
                    form.instance.image_composition()
                   
                    # desa el model pare per a desar els noms de la imatge composta /
                    # save the parent model to save the name of composed image
                    super(TotoAdmin, self).save_model(request, form.instance, form, change)


  • serializers
    • calculate a new field (can be related to any other model)
      • class TotoSerializer(serializers.ModelSerializer):
            ...
            new_field = serializers.SerializerMethodField('do_something')

            def do_something(self, obj):
                ...
    • user from request instead of by pk

Eclipse

  • Install
  • Import an existing Django project into Eclipse (not an Eclipse project)
    • How do I import existing projects/sources for a Django project into PyDev?
    • File -> New -> Project
      • PyDev -> PyDev Django Project
      • PyDev -> PyDev Project
    • Project name: my_project
    • Use default (/path/to/workspace/)
    • disable "Use default" / Browse: absolute_path_to_your_project_name
    • Interpreter
      • e.g. (if using virtualenv): /opt/PYTHON27/bin/python
    • this will create .project and .pydevproject into /path/to/workspace/my_project
    • select your project
      • PyDev -> Set as Django Project
      • Properties -> PyDev-PYTHONPATH -> String Substitution Variables
        • DJANGO_MANAGE_LOCATION: manage.py
        • DJANGO_SETTINGS_MODULE: my_project.settings
  • Breakpoints
    • debug response, even before reaching your code:
      • .../lib/python2.7/site-packages/django/core/handlers/wsgi.py: line 200 (WSGIHandler.__call__: return response)
  • Debug with shell
    • PyDev console -> Python console
      • import django
      • django.setup()
      • from django.conf import settings
      • settings.configure()
      • from my_app.models import *
      • ...

Bases de dades / Databases

  • Databases (1.8)
  • Multiple databases
  • Configuració de la base de dades / Database configuration


    • mysite/settings.py Linux packages
      pip install


      'ENGINE'
      CentOS

      MariaDB / MySQL
      'django.db.backends.mysql' yum install mariadb mariadb-server mariadb-devel
      MySQL-python
      PostgreSQL
      'django.db.backends.postgresql_psycopg2'
      yum install postgresql-server postgresql-devel
      psycopg2
      GIS
      'django.contrib.gis.db.backends.postgis'

      SQLite
      'django.db.backends.sqlite3'


  • Clear database
  • Dump
  • Migració / Migration (e.g. from SQLite to MariaDB)
  • Migration (model modification)

    • Django 1.6 / South
      Django 1.7
      create database
      syncdb
      migrate
      create migrations
      schemamigration
      makemigrations
      apply migrations
      migrate
      migrate
      note: in Django 1.7, an app will be affected by migrations only if it contains a "migrations" directory with an empty "__init__.py" file  in it 
    • Django 1.7 migrations (django.db.migrations)
      • Upgrading from South
        1. settings.py
          1. INSTALLED_APPS = (
                ...  
                #'south',
            )
        2. cd migration
        3. rm -f ????_* __init__.pyc
        4. python manage.py makemigrations
        5. python manage.py migrate
      • First time
        1. create database (for base Django):
          • python manage.py migrate
        2. start migrations (for applications):
          1. python manage.py makemigrations
          2. python manage.py migrate
        3. create a superuser:
      • Next times
        1. modify your model
        2. python manage.py makemigrations [my_app]
        3. [python manage.py migrate [my_app] --list]
        4. python manage.py migrate [my_app]
      • Squashing migrations
        • python manage.py squashmigrations my_app 0004
      • Unapply migrations
        • revert to status until migration 0007 (but no further)
          • python manage.py migrate my_app 0007
      • Restart from scratch
        • to remove all migrations and start from scratch:
          1. remove the project database:
            • mysql -P 3307 -u root -p
            • DROP DATABASE myprojectdb;
          2. create the database:
            • mysql -P 3307 -u root -p
            • CREATE DATABASE myprojectdb;
              GRANT ALL ON myprojectdb.* TO 'myuser'@'localhost' IDENTIFIED BY 'mypassword';
              FLUSH PRIVILEGES;
          3. cd my_app/migrations
          4. rm -f ????_*
          5. cd ../..
          6. python manage.py migrate
          7. python manage.py makemigrations
          8. python manage.py migrate
    • South
      • Instal·lació / Installation
        • pip install South
        • settings.py
          • INSTALLED_APPS = (
                ...  
                'south',
            )
        • python manage.py syncdb (to generate South own tables)
      • Primera vegada / First time
        • brand new app (no syncdb has been executed):
          • python manage.py schemamigration toto_app --auto
          • python manage.py migrate toto_app
        • existing apps (syncdb has been executed and therefore tables has been created) and VCS (e.g. git, subversion...) (Converting an app)
          • en el propi ordinador / in own computer
            • python manage.py convert_to_south toto_app
            • python manage.py schemamigration toto_app --initial
            • python manage.py migrate toto_app 0001 --fake
          • en altres ordinadors / in other computers (VCS up to date)
            • python manage.py migrate toto_app 0001 --fake
      • Següents vegades / Next times
        1. modify your model
        2. python manage.py schemamigration my_app --auto
        3. python manage.py migrate my_app (instead of python manage.py syncdb)
        4. if further modifications are done in your model, and you don't want to generate another migration:
          1. python manage.py schemamigration my_app --auto --update
          2. python manage.py migrate my_app
      • Llista de migracions / List migrations:
        • python manage.py migrate --list
    • Evolution
  • SQL
    • SQLite
      • python manage.py dbshell
        • sqlite> .help
  • No SQL
  • AWS RDS
    • Create a PostgreSQL database from AWS console:
      • Master user: my_master_user
      • Database: my_database
    • PostgreSQL GIS example:
      • setup_aws_rds.sh
        • #!/bin/bash
          rds_host=xxxx.yyyyyy.eu-west-1.rds.amazonaws.com

          master_user=my_master_user
          master_database=postgres
          database_name=my_database

          # create user
          user_name=my_user
          user_password=$(cat db_p.txt)
          psql -h ${rds_host} --username=${master_user} ${master_database} --command="CREATE USER $user_name WITH PASSWORD '${user_password}' CREATEDB;"

          # create postgis extension for
          my_database
          psql -h ${rds_host} --username=${master_user} ${database_name} --command="CREATE EXTENSION postgis;"
      • settings.py
        • DATABASES = {
              'default': {
                  'ENGINE': 'django.contrib.gis.db.backends.postgis',
                  'NAME': '
          my_database',
                  'USER': '
          my_user',
                  'PASSWORD': open(os.path.join(BASE_DIR, 'db_p.txt'),'r').read().strip(),
                  'HOST':'
          xxxx.yyyyyy.eu-west-1.rds.amazonaws.com',
                  'PORT': '',                      # Set to empty string for default.
              }
          }

  • Replicació / Replication

HTTP server

  • How to deploy with WSGI
  • Django documentation
    • Deploying static files
    • Cache
      • Django’s cache framework
      • Tipus / Types
      • Nivells / Levels
        • level
          mechanism
          Django
          cache only pieces


          The per-view cache

          • urls.py
            • from django.views.decorators.cache import cache_page
              url(r'^myview/$', cache_page(60 * 15)(views.MyView.as_view()), name='my-detail' ),
          • views.py
            • from rest_framework_extensions.cache.decorators import cache_response
              @cache_response(settings.CACHE_TIMEOUT_MYMODEL_MYDETAILVIEW, key_func=MyKeyConstructor())
          The per-site cache

          • settings.py
            • MIDDLEWARE = [
                  'django.middleware.cache.UpdateCacheMiddleware', # sets HTTP headers
              : Expires: current date + CACHE_MIDDLEWARE_SECONDS, Cache-Control: max_age=CACHE_MIDDLEWARE_SECONDS
                  'django.middleware.common.CommonMiddleware',
                  ...
                  'django.middleware.cache.FetchFromCacheMiddleware',
              ]
            • CACHE_MIDDLEWARE_ALIAS
              CACHE_MIDDLEWARE_SECONDS
              (overwritten by max_age in cache_control)
              CACHE_MIDDLEWARE_KEY_PREFIX

          downstream
          • browser-based cache
          • squid
          HTTP headers
          • views.py
            • from django.views.decorators.cache import cache_control
              @cache_control(max_age=settings.CACHE_TIMEOUT_MYMODEL_MYDETAILVIEW)

      • Buida la cache / Purge cache
        • ./manage.py shell
          from django.core.cache import cache; cache.clear()
      • Exemples / Examples
        • settings.py
          • CACHES = {
                'default': {
                    'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
                    #'LOCATION': 'unix:/var/run/memcached/memcached.sock',
                    'LOCATION': '127.0.0.1:11211',
                    #'TIMEOUT': 60 * 5,
                    'VERSION': 1,
                }
            }

        • djangorestframework
          • only for a specific view
            • urls.py
              • from django.views.decorators.cache import cache_page

                urlpatterns = patterns('',
                    url(r'^myview/$', cache_page(60 * 15)(views.MyView.as_view()), name='my-detail' ),
                )
          • Viewset
            • DRF-Extensions
            • views.py
              • from django.views.decorators.cache import cache_control
                from rest_framework_extensions.cache.decorators import (
                   
                cache_response
                )
                from rest_framework_extensions.key_constructor.constructors import (
                    DefaultKeyConstructor
                )
                from rest_framework_extensions.key_constructor.bits import (
                    KwargsKeyBit,
                )

                class MyKeyConstructor(DefaultKeyConstructor):
                    # this is needed to differentiate responses that have a parameter inside url (internally is a kwarg)
                    # .../<some_args>/my_detail_view/
                    kwargs = KwargsKeyBit()

                class MyModelViewSet(viewsets.ModelViewSet):
                    @
                cache_control(max_age=settings.CACHE_TIMEOUT_MYMODEL_MYDETAILVIEW)
                    @cache_response(settings.CACHE_TIMEOUT_MYMODEL_MYDETAILVIEW, key_func=MyKeyConstructor())
                    @detail_route( serializer_class=MyDetailViewSerializer )
                    def my_detail_view(self, request, *args, **kwargs):
                        ...

  • Django book
  • MS Windows
  • A Comparison of Web Servers for Python Based Web Applications
  • Resum de permisos d'execució / Execution permission summary



    user
    access

    django execution from
    service script (systemd)
    default user
    user specification at
    suggested value
    must have access to
    specified at suggested value
    permissions
    CLI
    manage.py migrate




    django logger files myproject/myproject/settings.py
    (GroupWriteRotatingFileHandler)
    /var/log/django/myproject.log useradd django
    usermod -a -G django centos
    mkdir -p /var/log/django
    chown django.django /var/log/django/
    chmod g+ws /var/log/django
    service
    nginx
    /usr/lib/systemd/system/nginx.service


    /etc/nginx/nginx.conf
    user nginx;
    log files

    /var/log/nginx/



    uwsgi sock files /etc/nginx/conf.d/myproject_nginx.conf server unix:///var/lib/uwsgi/myproject.sock; (nginx SELinux)
    uwsgi /usr/lib/systemd/system/emperor.uwsgi.service
    root.root

    /etc/uwsgi/emperor.ini
    uid = django
    gid = django
    /etc/uwsgi/vassals/myproject.ini
    socket = /var/lib/uwsgi/myproject.sock
    chmod-socket = 666
    mkdir /var/lib/uwsgi/
    chown django.django /var/lib/uwsgi/


    django logger files
    myproject/myproject/settings.py /var/log/django/myproject.log
    (mireu la primera fila a sobre / see first row above)
    celery
    /usr/lib/systemd/system/celery.service



    /usr/lib/systemd/system/celery.service
    User=django
    Group=django
    pid file /etc/sysconfig/celery
    /var/run/celery/%N.pid

    log file /etc/sysconfig/celery /var/log/celery/%N.log
    django logger files myproject/myproject/settings.py

    celerybeat /usr/lib/systemd/system/celerybeat.service

    /usr/lib/systemd/system/celerybeat.service User=django
    Group=django
    pid file /etc/sysconfig/celery /var/run/celery/beat.pid

    log file /etc/sysconfig/celery

  • Option 1: Start the development http server
    • cd djcode/mysite
    • python manage.py runserver [[0.0.0.0]:8080]
  • Option 1.2: django-devserver
  • https development server
  • Option 2: Integration with Apache + mod_wsgi:
    • How to use Django with Apache and mod_wsgi (1.4) (1.6)
    • Install Apache and mod_wsgi (1.3)
    • Newbie Mistakes (Django)
    • Serving favicon in an Django App using Apache
    • Designating the settings
    • Steps to setup (see also: Production, script for Apache installation):
      1. make sure that your mod_wsgi version matches the Python version:
        • ldd /etc/httpd/modules/mod_wsgi.so
          • libpythonX.Y.so ...
      2. (encara cal?/still needed?) modify /etc/httpd/modules.d/B23_mod_wsgi.conf (Mageia), /etc/httpd/conf.d/wsgi.conf (CentOS) , or /etc/apache2/mods-available/wsgi.conf (Debian, Ubuntu):
        • #WSGIScriptAlias / /home/user/src/djcode_1.4/mysite/mysite/wsgi.py
          # only when running a single django:
          # parent directory
          of parent directory (grandparent) of settings.py:
          WSGIPythonPath /home/user/src/djcode_1.4/mysite/
          # if you are using virtualenv
          , replace the previous line by this one:
          #
          WSGIPythonPath /home/user/src/djcode_1.4/mysite/:/path/to/your/venv/lib/python2.X/site-packages
      3. Note: for multiple instances on Apache (wsgi)
        • Basic configuration
        • first_site.conf
          • # only when running a single django:
            # parent directory of parent directory (grandparent) of settings.py, and virtualenv path:
            #WSGIPythonPath /home/ubuntu/my_project1/
            :/path/to/your/venv/lib/python2.X/site-packages

            <VirtualHost *:8000>
              # to run several djangos
              WSGIDaemonProcess example1.com python-path=/home/ubuntu/my_project1/:/path/to/your/venv/lib/python2.X/site-packages
              WSGIProcessGroup example1.com
            </VirtualHost>

        • second_site.conf
          • # only when running a single django:
            # parent directory of parent directory (grandparent) of settings.py, and virtualenv path:
            #WSGIPythonPath /home/ubuntu/my_project2/
            :/path/to/your/venv/lib/python2.X/site-packages

            <VirtualHost *:8008>
              # to run several djangos
              WSGIDaemonProcess example2.com python-path=/home/ubuntu/my_project2/
            :/path/to/your/venv/lib/python2.X/site-packages
              WSGIProcessGroup example2.com
            </VirtualHost>
        • Deploying multiple django apps on Apache with mod_wsgi
        • /etc/httpd/modules.d/B23_mod_wsgi.conf
          • WSGIPythonPath /var/www/site1:/var/www/site2
      4. create the file /etc/{httpd,apache2}/conf/sites-available/django_project (to avoid "Access forbidden" Error 403) or /etc/httpd/conf/vhosts.d/django_project.conf
        • Listen 8008

          # parent directory of parent directory (grandparent) of settings.py:
          # IMPORTANT: if using virtualenv, don't put python-path here; put it in wsgi.conf (
          WSGIPythonPath).
          WSGIPythonPath /home/user/src/djcode_1.4/mysite/

          <VirtualHost *:8008>
            #WSGIDaemonProcess python-path=
          /home/user/src/djcode_1.4/mysite/
           
            # to avoid "Authentication credentials were not provided"
          error message
            # with django-rest-framework
            WSGIPassAuthorization On
           
            # full path of wsgi.py:
            WSGIScriptAlias / /home/user/src/djcode_1.4/mysite/mysite/wsgi.py
           
            # parent directory of wsgi.py:
           
          <Directory /home/user/src/djcode_1.4/mysite/mysite>
              <Files wsgi.py>
                  Order deny,allow
                  Allow from all
              </Files>
            </Directory>
          </VirtualHost>
        • Alias /media/ /path/to/media/
          <Directory /path/to/media/>
            Order deny,allow
            Allow from all
          </Directory>
      5. Comproveu els permisos de grup del fitxer de la base de dades i els seus directoris per sobre / Check groups permissions of database file and their ancestor directories:
        • if database is in a system directory:
          • chown apache.apache -R /var/www/django_dir/
          • chgrp `grep "^Group" /etc/httpd/conf/httpd.conf | awk '{print $2}'` -R /var/www/django_dir/
          • chmod g+w -R /var/www/django_dir/
        • if database is in a user directory:
          • add group www-data or apache (or whatever defined in Group directive in httpd.conf) as a secondary group of the user that owns the directory:
            • # usermod -a -G www-data user_name
          • sudo chown www-data. -R polls
          • maybe changing only the group is enough:
            • CentOS:
              •  # chgrp apache service.db
            • Ubuntu:
              • sudo chgrp www-data service.db
            • sudo chmod 664 service.db
      6. (needed? no) modify wsgi.py:
        • import sys
          sys.path.append('/home/user/src/djcode_1.4/mysite/')
      7. Install and enable mod_wsgi:
        • Mageia
          • urpmi apache-mod_wsgi
          • httpd.conf (cal?/needed?)
            • LoadModule wsgi_module extramodules/mod_wsgi.so
          • cd /etc/httpd/conf/vhosts.d; ln -s ../sites-available/django-site 01_django-site.conf
        • Ubuntu
          • sudo apt-get install libapache2-mod-wsgi
          • cd /etc/apache2/mods-enabled; ln -s ../mods-available/wsgi.conf .; ln -s ../mods-available/wsgi.load .
          • cd /etc/apache2/conf/sites-enabled; ln -s ../sites-available/django-site .
        • CentOS
          • yum install mod_wsgi
      8. Copieu els fitxers estàtics / Copy the static files
      9. Media files
      10. Django settings.py (IMPORTANT: path must be absolute; constructions like "os.path.abspath(..." will NOT work (but they did for debugging http Django server))
    • Script for Apache installation:
      • django_project/install_lamp/django_project.conf (or use script create_apache_conf.sh)
        • Listen 8008

          # parent directory of parent directory (grandparent) of settings.py, and virtualenv path:
          WSGIPythonPath /var/www/django_project/:/opt/PYTHON27/lib/python2.7/site-packages/

          <VirtualHost *:8008>
            #WSGIDaemonProcess python-path=/home/user/src/djcode_1.4/mysite/
           
            # to avoid "Authentication credentials were not provided"
          error message
            # with django-rest-framework
            WSGIPassAuthorization On

            # full path of wsgi.py:
            WSGIScriptAlias / /var/www/django_project/django_project/wsgi.py
           
            # parent directory of wsgi.py:
            <Directory /var/www/django_project/django_project/>
              <Files wsgi.py>
                  #Order deny,allow
                  #Allow from all
                 
          Require all granted
              </Files>
            </Directory>

            # static files
            #   Alias <settings.STATIC_URL> <settings.STATIC_ROOT>
            #AliasMatch ^/([^/]*\.css) /var/www/django_project/collected_static/styles/$1
            Alias /static/ /var/www/django_project/collected_static/
            <Directory /var/www/django_project/collected_static/>
              #Order deny,allow
              #Allow from all
             
          Require all granted
            </Directory>

            # media files
            #   Alias <setttings.MEDIA_URL> <settings.MEDIA_ROOT>
            Alias /mm/ /var/www/django_project/media/
            <Directory /var/www/django_project/media/>
              #Order deny,allow
              #Allow from all
             
          Require all granted
            </Directory>

          </VirtualHost>
      • django_project/install_lamp/create_apache_conf.sh
        • #!/bin/bash
          EXPECTED_ARGS=2
          if [ $# -ne $EXPECTED_ARGS ]
          then
              cat <<EOF
          Usage: `basename $0` base_dir project_name

          Examples:
          `basename $0` /var/www/ project_name
          `basename $0` /home/user/src/ project_name
          EOF
              exit 1
          fi

          BASE_DIR=$1
          PROJECT_NAME=$2

          VIRTUALENV_DIR=/opt/PYTHON27/lib/python2.7/site-packages/
          LISTEN_PORT=80

          if [ ${LISTEN_PORT} -eq 80 ]
          then
              cat > ${PROJECT_NAME}.conf <<EOF
          EOF
          else
              cat > ${PROJECT_NAME}.conf <<EOF
          Listen ${LISTEN_PORT}
          EOF
             
          fi

          cat >> ${PROJECT_NAME}.conf <<EOF

          # parent directory of parent directory (grandparent) of settings.py, and virtualenv path:
          WSGIPythonPath ${BASE_DIR}${PROJECT_NAME}/:${VIRTUALENV_DIR}

          <VirtualHost *:${LISTEN_PORT}>
            #WSGIDaemonProcess python-path=/home/user/src/djcode_1.4/mysite/

            # full path of wsgi.py:
            WSGIScriptAlias / ${BASE_DIR}${PROJECT_NAME}/${PROJECT_NAME}/wsgi.py
           
            # parent directory of wsgi.py:
            <Directory ${BASE_DIR}${PROJECT_NAME}/${PROJECT_NAME}/>
              <Files wsgi.py>
                  #Order deny,allow
                  #Allow from all
                  Require all granted
              </Files>
            </Directory>

            # static files
            #AliasMatch ^/([^/]*\.css) ${BASE_DIR}${PROJECT_NAME}/collected_static/styles/$1
            Alias /static/ ${BASE_DIR}${PROJECT_NAME}/collected_static/
            <Directory ${BASE_DIR}${PROJECT_NAME}/collected_static/>
              #Order deny,allow
              #Allow from all
              Require all granted
            </Directory>

            # media files
            Alias /media/ ${BASE_DIR}${PROJECT_NAME}/media/
            <Directory ${BASE_DIR}${PROJECT_NAME}/media/>
              #Order deny,allow
              #Allow from all
              Require all granted
            </Directory>

          </VirtualHost>

          EOF


      • django_project/install_lamp/install_apache.sh
        • #!/bin/bash
          EXPECTED_ARGS=3
          if [ $# -ne $EXPECTED_ARGS ]
          then
              cat <<EOF
          Usage: `basename $0` project_name source_webapp_dir destination_webapp_dir

          Examples:
          `basename $0` django_project ~user/src/ /var/www/
          `basename $0` django_project ~user/src/ ~user/src/

          Actions:
          - copy project_name.conf to apache configuration directory (e.g. /etc/httpd/conf/webapps.d/)
            Make sure that it points to correct directories: destination_webapp_dir
          - recursively copy source_webapp_dir/project_name to destination_webapp_dir/project_name, only if they are different
          - change the owner and group of the files and directories to the right ones

          The following typical OS are automatically detected:
          - Red Hat, CentOS, Mageia, ...
          - Debian, Ubuntu, ...
          - MAC OSX (MAMP)
          EOF
              exit 1
          fi

          project_name=$1

          source_webapp_dir=$2
          destination_webapp_dir=$3

          #webapp_dir=/var/www/${project_name}

          virtual_env_source=/opt/PYTHON27/

          is_debian=0

          # Red Hat, CentOS, Mageia, ...
          if [ -f /etc/httpd/conf/httpd.conf ]
          then
              httpd_config=/etc/httpd/conf/httpd.conf
              webapp_conf_dir=/etc/httpd/conf/webapps.d/
           
              usuari_httpd=`grep "^Group" $httpd_config | awk '{print $2}'`
              grup_httpd=`grep "^User" $httpd_config | awk '{print $2}'`
          fi

          # MAC OSX
          if [ -f /Applications/MAMP/conf/apache/httpd.conf ]
          then
              httpd_config=/Applications/MAMP/conf/apache/httpd.conf
              webapp_conf_dir=/Applications/MAMP/conf/apache/extra
              webapp_dir=~/sites/${project_name}
          fi

          # Debian, Ubuntu, ...
          if [ -f /etc/apache2/apache2.conf ]
          then
              is_debian=1

              httpd_config=/etc/apache2/apache2.conf
              #webapp_conf_dir=/etc/apache2/conf.d/
              webapp_conf_dir=/etc/apache2/sites-available/
           
              usuari_httpd=`awk -F= '/APACHE_RUN_USER/ {print $2}' /etc/apache2/envvars`
              grup_httpd=`awk -F= '/APACHE_RUN_GROUP/ {print $2}' /etc/apache2/envvars`
          fi

          # collect static files
          source ${virtual_env_source}bin/activate
          python ../manage.py collectstatic
          deactivate

          # còpia recursiva del virtualenv


          # còpia recursiva del project_name
          if [ $source_webapp_dir != $destination_webapp_dir ]
          then
              echo "copying application $project_name from $source_webapp_dir to $destination_webapp_dir"

              #mkdir -p $webapp_dir
              #rsync -a --exclude='.git' --exclude='install_lamp' .. $webapp_dir
              mkdir -p ${destination_webapp_dir}/${project_name}
              rsync -a --exclude='.git' --exclude='install_lamp' ${source_webapp_dir}/${project_name} ${destination_webapp_dir}
          fi

          # echo "changing permissions for $destination_webapp_dir"
          # #chown ${usuari_httpd}.${grup_httpd} -R $webapp_dir
          # chown ${usuari_httpd}.${grup_httpd} -R ${destination_webapp_dir}

          # webapp config
          echo "copying config file ${project_name}.conf to ${webapp_conf_dir}"
          cp ${project_name}.conf ${webapp_conf_dir}

          if [ ${is_debian} -eq 1 ]
          then
              # disable default site
              a2dissite 000-default

              # enable added site
              a2ensite ${project_name}
             
              # activate new configuration
              service apache2 reload
          fi

    • Problemes / Problems
      • CentOS: Apache
      • "Access forbidden!" 403
        • Comproveu els permisos d'usuari UNIX del directori / Check the UNIX user permissions of the directory
        • Comproveu que al directori on hi ha wsgi.py no s'hi accedeixi mitjançant un enllaç simbòlic
          • Solució:
            • ? afegir directiva FollowSymbolicLink
      • "unable to open database file"
      • Error 500: Server error:
        • (?) WSGIPythonPath needs to be specified (on /etc/httpd/modules.d/B23_mod_wsgi.conf or .../mods-available/wsgi.conf)
      • Internal Server Error
        • /var/log/httpd/error_log
          • ImportError: Could not import settings ... (Is it on sys.path?): No module named ...
          • ImportError: No module named bootstrap_toolkit
            • Solution:
              • ...
      • Authentication credentials were not provided
      • Admin: redirect to login page (Sessions)
      • Admin:
        • ... [error] 9989#0: *1346 readv() failed (104: Connection reset by peer) while reading upstream, client: 37.15.63.190, server: ..., request: "POST /admin/... HTTP/1.1", ...
          • The number of GET/POST parameters exceeded settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.
            • Solució / Solution
              • settings.py
                • DATA_UPLOAD_MAX_NUMBER_FIELDS = None
  • Option 3: nginx + ...
    • uWSGI
      • websocket
      • How to use Django with uWSGI (Django)
      • Setting up Django and your web server with uWSGI and nginx (uWSGI)
      • Managing the uWSGI server
      • Things to know (best practices and “issues”) READ IT !!!
      • without nginx (the web client <-> uWSGI <-> Python)
        • wget https://raw.githubusercontent.com/nginx/nginx/master/conf/uwsgi_params
        • uwsgi --http :8000 --module mysite.wsgi
      • with nginx (the web client <-> the web server <-> the socket <-> uWSGI <-> Python)
        • Setting up Django and your web server with uWSGI and nginx
        • Permisos d'execució / Execution permissions
        • nginx config
          • /etc/nginx/conf.d/mysite_nginx.conf
            • # the upstream component nginx needs to connect to
              upstream django_mysite {
                  server unix:///var/lib/uwsgi/mysite.sock; # for a file socket
                  #server unix:///home/username/src/uwsgi-tutorial/mysite/mysite.sock; # for a file socket
                  #server 127.0.0.1:8001; # for a web port socket (we'll use this first)
              }

              # configuration of the server
              server {
                  # the port your site will be served on
                  listen      8000;
                  # the domain name it will serve for
                  #server_name .example.com; # substitute your machine's IP address or FQDN
                  charset     utf-8;

                  # max upload size
                  client_max_body_size 75M;   # adjust to taste

                  # Django media
                  location /media  {
                      alias /path/to/your/mysite/media;  # your Django project's media files - amend as required
                  }

                  location /static {
                      alias /path/to/your/mysite/static; # your Django project's static files - amend as required
                  }

                  # Finally, send all non-media requests to the Django server.
                  location / {
                      uwsgi_pass  django
              _mysite;
                      # to avoid message: "504 Gateway Time-out"
                      uwsgi_read_timeout 180s;
                      #include /home/username/src/uwsgi-tutorial/mysite/uwsgi_params; # the uwsgi_params file you installed
                     
              include /etc/uwsgi/uwsgi_params
                  }
              }

        • uWSGI config
          • Nginx support
          • Instal·lació / Installation
            • virtualenv /opt/p27
            • source /opt/p27/bin/activate
            • virtualenv /path/to/mysite/env
            • source /path/to/mysite/env/bin/activate
            • pip install uwsgi
          • /etc/uwsgi/uwsgi_params
          • usage modes
            • directly
              • uwsgi --socket mysite.sock --module mysite.wsgi --chmod-socket=666
            • from ini config file
              • Configuring uWSGI to run with a .ini file
              • create and give permissions to dir for socket:
                • sudo mkdir -p /var/lib/uwsgi/
                • sudo chown django.django /var/lib/uwsgi/
              • /path/to/your/mysite/mysite_uwsgi.ini
                • # mysite_uwsgi.ini file
                  [uwsgi]

                  # Django-related settings
                  # the base directory (full path)
                  chdir           = /path/to/mysite
                  # Django's wsgi file
                  module          = mysite.wsgi
                  # for weblate, use wsgi-file instead of module:
                  #wsgi-file       = /path/to/weblate/weblate/wsgi.py
                  # the virtualenv (full path)
                  #home            = /opt/p27

                  home            = /path/to/mysite/env

                  # process-related settings
                  # master
                  master          = true
                  # maximum number of worker processes
                  processes       = 10
                  # the socket (use the full path to be safe)
                  socket          = /var/lib/uwsgi/mysite.sock
                  # ... with appropriate permissions - may be needed
                  chmod-socket    = 666  
                  # clear environment on exit
                  vacuum          = true

              • uwsgi -i mysite_uwsgi.ini
            • Emperor mode
              • /etc/uwsgi/vassals/
                • mysite1_uwsgi.ini -> /path/to/your/mysite/mysite1_uwsgi.ini
                • mysite2_uwsgi.ini -> /path/to/your/mysite/mysite2_uwsgi.ini
                • ...
              • sudo uwsgi --emperor /etc/uwsgi/vassals --uid django --gid django
              • Problemes / Problems
                • uwsgi[...]: bind(): No such file or directory [core/socket.c line 230]
                  • check that parent dir of /var/lib/uwsgi/mysite.sock exists and has write permissions for user specified in /etc/uwsgi/emperor.ini
                • Error 500
                  • /var/log/messages
                    • uwsgi: --- no python application found, check your startup logs for errors ---
                  • try manual execution:
                    • sudo systemctl stop emperor.uwsgi.service
                    • sudo /path/to/virtualenv/bin/uwsgi -i /etc/uwsgi/emperor.ini
                    • sudo /path/to/virtualenv/bin/uwsgi --emperor /etc/uwsgi/vassals --uid django --gid django
                    • sudo /path/to/virtualenv/bin/uwsgi -i /etc/uwsgi/vassals/mysite1_uwsgi.ini --uid django --gid django
                    • verbose when executing weblate:
                      • django.core.exceptions.ImproperlyConfigured: Requested setting BASE_DIR, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
                        • Solution
                          • in weblate_uwsgi.ini, use wsgi-file instead of module:
                            • # Django's wsgi file
                              #module          = weblate.wsgi
                              wsgi-file       = /path/to/weblate/weblate/wsgi.py

                          • alternative: in manual execution:
                            • sudo -i
                            • export DJANGO_SETTINGS_MODULE=weblate.settings; /path/to/virtualenv/bin/uwsgi -i /etc/uwsgi/emperor.ini
                      • OSError: DATA_DIR /home/myuser/weblate/weblate/../data is not writable!
                        • Solutions
                          • Install weblate in an ad-hoc directory
                          • Install weblate inside home directory (not recommended)
                            • check that there is a user django and that your user belongs to django group:
                              • useradd django
                              • usermod -a -G django myuser
                            • chown django.django -R /home/myuser/weblate/data
                            • chmod g+ws /home/myuser/weblate/data
                  • check ownership problems (see problems with uwsgi and httplib2 when user executing uwsgi and user who installed pip packages are not the same):
                    • change uid, gid in ini file specified in call to uwsgi binary (usually from /usr/lib/systemd/system/emperor.uwsgi.service), to match username and group of user who pip-installed the packages in virtualenv
          • start script (systemd)
            • useradd django
            • /etc/uwsgi/emperor.ini
              • [uwsgi]
                emperor = /etc/uwsgi/vassals
                uid = django
                gid = django

            • /etc/systemd/system/emperor.uwsgi.service
              • [Unit]
                Description=uWSGI Emperor
                After=syslog.target

                [Service]
                ExecStart=/path/to/mysite/env/bin/uwsgi --ini /etc/uwsgi/emperor.ini
                Restart=always
                KillSignal=SIGQUIT
                Type=notify
                StandardError=syslog
                NotifyAccess=all

                [Install]
                WantedBy=multi-user.target

    • Gunicorn

Settings

  • Ús / Usage
    • from django.conf import settings
  • Camins relatius / Relative paths
    • # already set in newest versions of Django:
      import os
      PROJECT_PATH = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))

      # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
      import os
      BASE_DIR = os.path.dirname(os.path.dirname(__file__))

    • DATABASES = {
          'default': {
              ...
              'NAME': PROJECT_PATH + '/sqlite.db',            # Or path to database file if using sqlite3.
              'NAME': os.path.join(BASE_DIR, 'sqlite.db'),   # Or path to database file if using sqlite3.
              ...
          }
      }

    • TEMPLATE_DIRS = (
          PROJECT_PATH + '/templates/'
      )

      TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]
  • Valors de settings des de plantilles / Settings values from templates
  • Producció / Production
  • Diversos fitxers de configuració / Several setting files
  • Contrasenyes (per a no tenir-les al git) / Passwords (to avoid having them in git)

Timezone

  • Timezones


  • settings

    from django.utils import timezone


    USE_TZ
    TIME_ZONE
    now
    timezone.is_naive(now)
    timezone.is_aware(now)
    import datetime
    now = datetime.datetime.now()
    -
    -
    datetime.datetime(2015, 2, 19, 16, 40, 13, 962574)
    True
    False
    from django.utils import timezone
    now = timezone.now()
    False
    -
    datetime.datetime(2015, 2, 19, 16, 40, 13, 962574)
    True
    -
    datetime.datetime(2015, 2, 19, 15, 40, 13, 962574, tzinfo=<UTC>)
    False True
  • Dates are stored in UTC into the database
  • settings.TIME_ZONE is used for user interaction (forms, list_display, ...)

CORS

Plantilles / Templates

  • Templates (1.6)
  • The Django template language
    • Elements
      • variable:
        • {{ variable|filter }}
        • convert a string to int:
          • {{ my_int|slugify }}
          • exemple:
            • {% if my_model.my_int|slugify == some_other_int_as_string_from_context %}{% endif %}
      • tag:
        • {% tag %}...{% end_tag %}
          • if, for, ...
      • comment:
        • {# my_comment #}
        • {% comment %}...my_comment...{% endcomment %}
    • Template inheritance
      • my_project/my_app/templates/my_app/view1.html
      • my_project/my_app/templates/admin/my_app/my_change_list_template.html
        • class TotoAdmin(admin.ModelAdmin)
              change_list_template = 'admin/my_app/my_change_list_template.html'
              ...

      • my_project/templates/
        • base.html
        • [my_app/]
          • view1.html
      • Exemples / Examples
        • Personalitza l'aspecte de l'admin

Forms

Seguretat / Security

Usuaris / Users

  • Usuaris / Users (used for authentication, e.g. in Tastypie) (també disponible a / also available from http://127.0.0.1:8000/admin/auth/user/):
    • Usuaris i admin / Users and admin
    • Creació / Creation
      • python manage.py shell
        >>> from django.contrib.auth.models import User
        >>> user = User.objects.create_user("nom_usuari","adreca@usuari","contrasenya")
        >>> user.is_staff = True
        >>> user.save()
    • Contrasenyes / Passwords
    • Test:
      • curl --user nom_usuari:contrasenya ...
    • Superuser (admin)
    • Django tips: extending the User model
    • Autenticació d'usuari / User authentication
      • User authentication in Django (1.4)
        • Authentication: verifies a user is who they claim to be
        • Authorization: determines what an authenticated user is allowed to do


        • Django
          django-rest-framework authentication


          (default)
          django-allauth
          (default)
          djangorestframework-jwt django-rest-auth


          authentication authentication
          social authentication
          authentication authorization (permissions)
          authentication authentication
          registration
          social authentication


          verifies a user is who they claim to be



          determines what an authenticated user is allowed to do




          pip install


          django-allauth


          djangorestframework-jwt django-rest-auth
          settings.py
          INSTALLED_APPS
          • 'django.contrib.auth'
          • 'django.contrib.contenttypes'
          • 'django.contrib.sites'
          • 'allauth'
          • 'allauth.account'
          • ...
          • # also adds admin section "Social Accounts":
            'allauth.socialaccount'
          • 'allauth.socialaccount.providers....'



          • 'rest_framework'
          • 'rest_framework.authtoken'
          • 'rest_auth'
          • ...
          • 'allauth'
          • 'allauth.account'
          • 'rest_auth.registration'
          • ...
          • 'allauth.socialaccount'
          • 'allauth.socialaccount.providers.facebook'
          MIDDLEWARE_CLASSES
          • 'SessionMiddleware'
          • 'AuthenticationMiddleware'
          • 'SessionAuthenticationMiddleware'








          AUTHENTICATION_BACKENDS
          • 'django.contrib.auth.backends.ModelBackend'
            • username (USERNAME_FIELD) / password
          • 'django.contrib.auth.backends.RemoteUserBackend'
          • # Needed to login by username in Django admin, regardless of `allauth`
            'django.contrib.auth.backends.ModelBackend',
          • # `allauth` specific authentication methods, such as login by e-mail
            'allauth.account.auth_backends.AuthenticationBackend',







          TEMPLATE_CONTEXT_PROCESSORS (<1.8)
          TEMPLATES[].OPTIONS.context_processors (>=1.8)
          • 'django.core.context_processors.request'

          ...





          • 'allauth.socialaccount.context_processors.socialaccount'
          REST_FRAMEWORK = {...}



          'DEFAULT_AUTHENTICATION_CLASSES': (...)
          'DEFAULT_PERMISSION_CLASSES': (...)  
          'DEFAULT_AUTHENTICATION_CLASSES': (...)
            • 'rest_framework_jwt.authentication.JSONWebTokenAuthentication'
          'DEFAULT_AUTHENTICATION_CLASSES': (...)


          (specific)
          • USERNAME_FIELD = ...
          • AUTH_USER_MODEL = ...
          • SITE_ID = 1
          • ACCOUNT_*
            • ACCOUNT_AUTHENTICATION_METHOD = 'username'
            • ACCOUNT_EMAIL_REQUIRED = True
            • ACCOUNT_EMAIL_VERIFICATION = 'optional'
            • ACCOUNT_EMAIL_SUBJECT_PREFIX = '[...]'
            • ACCOUNT_PASSWORD_MIN_LENGTH = 6
            • ACCOUNT_ADAPTER
            • ...


          • JWT_AUTH
            • 'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3600)
            • 'JWT_ALLOW_REFRESH': True
            • 'JWT_RESPONSE_PAYLOAD_HANDLER': 'my_app.utils.jwt_response_payload_handler'
          • REST_AUTH_SERIALIZERS
            • 'LOGIN_SERIALIZER':
            • 'TOKEN_SERIALIZER':
            • 'USER_DETAILS_SERIALIZER':
            • 'PASSWORD_RESET_SERIALIZER':
            • 'PASSWORD_RESET_CONFIRM_SERIALIZER':
            • 'PASSWORD_CHANGE_SERIALIZER':
          • REST_SESSION_LOGIN
          • OLD_PASSWORD_FIELD_ENABLED


          views.py





          from rest_framework.exceptions import PermissionDenied

          class MyModelViewSet(viewsets.ModelViewSet):
              # affects all methods in the class
              permission_classes = (permissions.IsAuthenticated,)

              # specific permissions for create method
              def create(self, request, *args, **kwargs):
                  # only staff users can create objects
                  if not request.user.is_staff:
                      raise PermissionDenied("Only staff users can create objects.")
                  return viewsets.ModelViewSet.create(self, request, *args, **kwargs)





          decorators in views.py

          django.contrib.auth.decorators
          allauth.account.decorators
          • @verified_email_required


          from rest_framework.decorators import detail_route

          class MyModelViewSet(viewsets.ModelViewSet):
              ...
              @detail_route(permission_classes=[IsAdminOrIsSelf])
              def my_function(self, request, **kwargs):




          authentication views in
          views.py


          Profile view






          from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
          from rest_auth.registration.views import SocialLoginView

          class FacebookLogin(SocialLoginView):
              adapter_class = FacebookOAuth2Adapter
          ...
          urls.py

          • url('^', include('django.contrib.auth.urls'))
          url(r'^accounts/', include('allauth.urls')),
          url(r'^accounts/profile/', YOUR_OWN_PROFILE_VIEW),



          • url(r'^api-token-auth/', 'rest_framework_jwt.views.obtain_jwt_token')
          • url(r'^api-token-verify/', 'rest_framework_jwt.views.verify_jwt_token')
          • url(r'^api-token-refresh/', 'rest_framework_jwt.views.refresh_jwt_token')
          url(r'^rest-auth/', include('rest_auth.urls'))
             
          # needed by rest-auth/password/reset/
          # https://github.com/Tivix/django-rest-auth/issues/63
          url(r'^', include('django.contrib.auth.urls')),
          (r'^rest-auth/registration/', include('rest_auth.registration.urls'))
          url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login')
          ...
          templates

          • registration/login.html
          • ...








          entry points

          http://127.0.0.1:8000
          • /login
          • /logout
          • /password_change
          • /password_reset
          • /password_reset/done
          • /reset
          • /reset/done
          http://127.0.0.1:8000/accounts
          • /signup
          • /login
          • /logout
          • /password/change
          • /password/set
          • /inactive
          • /email
          • /confirm-email
          • /password/reset
          • /password/reset/done
          • /password/reset/key
          • /password/reset/key/done
          http://127.0.0.1:8000/accounts
          • /social
          • /twitter
          • /facebook
          • ...



          http://127.0.0.1:8000/rest-auth
          • /login
          • /logout
          • /password/reset
          • /password/reset/confirm
          • /password/change
          • /user
          http://127.0.0.1:8000/rest-auth
          • /registration
          • /registration/verify-email
          http://127.0.0.1:8000/rest-auth
          • /facebook
          • ...
          usage with requests




          import requests
          import json
          from pprint import pprint

          server_url = 'http://127.0.0.1:8000'

          # get something with basic authentication
          r = requests.get('%s/v1/totos/' % server_url, ... )
          pprint( r.json() )

          import requests
          import json
          from pprint import pprint

          server_url = 'http://127.0.0.1:8000'

          # login
          password = open('user1_p.txt', 'r').read().strip()
          payload_credentials = {
                                 'username': 'user1',
                                 'password': password,
                                 }
          r = requests.post(server_url+'/api-token-auth/', data=payload_credentials)
          pprint( r.json() )
          token = r.json()['token']

          # get something
          r = requests.get('%s/v1/totos/' % server_url, headers={'Authorization':'JWT %s'%token} )
          pprint( r.json() )
          import requests
          import json
          from pprint import pprint

          server_url = 'http://127.0.0.1:8000'

          # login
          password = open('user1_p.txt', 'r').read().strip()
          payload_credentials = {
                                 'username': 'user1',
                                 'password': password,
                                 }
          r = requests.post(server_url+'/rest-auth/login/', data=payload_credentials)
          pprint( r.json() )
          key = r.json()['key']

          # get something (e.g. user info)
          r = requests.get(backend_url+'/rest-auth/user/', headers={'Authorization':'Token %s'%key})
          pprint( r.json() )



          info




          • each call (if required) must contain username/password

          • first call to api-token-auth to get a token
          • next calls to your api must contain the token (Authorization: JWT)
          • /user gives information about the current user
          • Update UserProfile
            • USER_DETAILS_SERIALIZER
          • registration of new users
          • implements a user viewset for you



          authentication
          authentication
          social authentication
          authentication


          authentication
          registration
          social authentication


          default
          django-allauth
          (default)

          djangorestframework-jwt django-rest-auth


          Django
          django-rest-framework authentication

        • Using the Django authentication system
        • Creating superusers
        • Customizing authentication in Django (1.5)
          • Extending the existing User model / Substituting a custom User model
            • Use cases
              • use case 1: additional fields, not used to authenticate (profiles)
                • Properly Admin’ing User Profile Models
                  • IntegrityError in admin solved
                • Django < 1.5
                • my_app/models.py
                  • from django.contrib.auth.models import User

                    class UserProfile(models.Model):
                        user = models.OneToOneField(User)
                        department = models.CharField(max_length=100)
                  • from django.db.models.signals import post_save
                    from django.dispatch import receiver
                    from django.db import transaction

                    @receiver(post_save, sender=User)
                    def create_user_profile(sender, instance, **kwargs):
                        """
                        Create a profile when a user is created.
                        """
                        # do not create a userprofile when coming from loaddata
                        # TODO: consider check of kwargs.get('created', True)
                        #   (http://stackoverflow.com/questions/3499791/how-do-i-prevent-fixtures-from-conflicting-with-django-post-save-signal-code)
                        if not kwargs.get('raw', False):
                            try:
                                with transaction.atomic():
                                    user_profile = UserProfile.objects.get_or_create(user=instance)
                            except Exception as e:
                                print e

                • my_app/admin.py
                  • from django.contrib import admin
                    from django.contrib.auth.admin import UserAdmin
                    from django.contrib.auth.models import User
                    from django.contrib.auth import get_user_model

                    from my_user_profile_app.models import UserProfile

                    # Define an inline admin descriptor for UserProfile model
                    # which acts a bit like a singleton
                    class UserProfileInline(admin.StackedInline):
                        model = UserProfile
                        can_delete = False
                        verbose_name_plural = 'employee'

                    # Define a new User admin
                    class CustomUserAdmin(UserAdmin):
                        #inlines = (UserProfileInline, )

                        list_display = ('email','username','department',)
                        search_fields = ('username', 'first_name', 'last_name', 'email','userprofile__department',)

                        # used by list view
                        def department(self,item):
                            return item.userprofile.department

                        # used by detail view
                        def change_view(self, request, object_id, form_url='', extra_context=None):
                            self.inlines = (UserProfileInline,)

                            return super(UserAdmin, self).change_view(
                                request, object_id, form_url=form_url, extra_context=extra_context)

                    # Re-register UserAdmin
                    admin.site.unregister(get_user_model())
                    admin.site.register(get_user_model(), CustomUserAdmin)
              • use case 2: authentication must be done using a field different from "username"
              • use case 3: authentication must be done using a new field ('dni')
                • Django < 1.5
                • Django >= 1.5
                • Notes:
                  • not supported by makemigrations
                  • not suited for reusable applications
                • settings.py
                  • AUTH_USER_MODEL = 'my_app.MyUser'
                • my_app/models.py
                  • from django.contrib.auth.models import AbstractUser
                    class MyUser(AbstractUser):
                        dni = models.CharField("DNI", max_length=9, unique=True)


                        class Meta:
                            db_table = 'auth_user'
                            verbose_name = _('MyUser')
                            verbose_name_plural = _('MyUsers')

                • in your other py files that reference User:
                  • from django.conf import settings
                    #user = models.ForeignKey(User)
                    user = models.ForeignKey(settings.AUTH_USER_MODEL)

                • or add (to avoid error message: "Manager isn't available; User has been swapped for..." when accessing to admin):
                  • from django.contrib.auth import get_user_model
                    User = get_user_model()
                • my_app/admin.py (per a poder gestionar les contrasenyes i els camps addicionals / to deal with passwords and added fields)
        • views.login
      • Curso Django: gestión de usuarios
      • Authenticating against Django’s user database from Apache
      • How to use sessions
      • Multi-/Two-factor authentication
      • Social authentication
        • Signing up and signing in
        • What's the best solution for OpenID with Django? [closed]
        • django-allauth
          • Readthedocs
          • Can be used as:
            • extension for default authentication
            • social authentication
              • SOCIALACCOUNT_*
          • The missing django-allauth tutorial
          • Signing Up and Signing In: Users in Django with Django-AllAuth
          • Advanced usage
            • Templates
              • lib/python2.7/site-packages/allauth/templates/account/email/
                my_app/templates/account/email/
                • email_confirmation_subject.txt
                • email_confirmation_signup_subject.txt
                • password_reset_key_subject.txt
                • email_confirmation_message.txt
                • email_confirmation_signup_message.txt
                • password_reset_key_message.txt
              • i18n
                • lib/python2.7/site-packages/django/contrib/admin/locale/ca/LC_MESSAGES/django.po
                • lib/python2.7/site-packages/allauth/locale/xx/
            • Custom url
            • Custom account adapter to generate an external url to confirm email address:
              • my_project/settings.py
                • ACCOUNT_ADAPTER = 'my_app.adapter.MyAccountAdapter'

                  # url to appear on email message to verify email address
                  CONFIRM_EMAIL_URL_TEMPLATE="http://www.toto.org/confirm-email/{key}/"

              • my_app/adapter.py
                • from django.conf import settings
                  from allauth.account.adapter import DefaultAccountAdapter

                  class MyAccountAdapter(DefaultAccountAdapter):
                     
                      def get_email_confirmation_url(self, request, emailconfirmation):
                          absolute_url = settings.CONFIRM_EMAIL_URL_TEMPLATE.format(key=emailconfirmation.key)
                          return absolute_url
              • http://www.toto.org/confirm-email/<received_key>/
                • when using django-rest-auth, this page must send a POST to API REST server. Curl equivalent:
                  • curl -X POST -H 'Content-Type: multipart/form-data' -F key=${received_key} ${backend_url}/rest-auth/registration/verify-email/
            • Custom reset password email message
              • allauth/account/forms.py
                • ResetPasswordForm
            • Delete account
          • With django-rest-framework
            • Plug in django-allauth as endpoint in django-rest-framework
            • django-rest-auth
              • Documentation
              • FAQ
                • How can I update UserProfile assigned to User model?
                  • my_app/serializers.py
                    • from rest_auth.serializers import UserDetailsSerializer

                      class UserSerializer(UserDetailsSerializer):
                          company_name = serializers.CharField( source='userprofile.screen_name' )

                          def update(self, instance, validated_data):
                              profile_data = validated_data.pop('userprofile', {})
                             
                              instance = super(UserSerializer, self).update(instance, validated_data)

                              # get and update user profile               
                              profile = instance.userprofile
                              for attr, value in profile_data.items():
                                      setattr(profile, attr, value)
                              profile.save()
                             
                              return instance

                          class Meta(UserDetailsSerializer.Meta):
                              fields = UserDetailsSerializer.Meta.fields + ('company_name',)

                  • settings.py
                    • REST_AUTH_SERIALIZERS = {
                          'USER_DETAILS_SERIALIZER': 'my_app.serializers.UserSerializer'
                      }

              • Installation
                • pip install django-rest-auth
              • AngularJS
              • Problemes / Problems
                • NoReverseMatch at /rest-auth/password/reset/
                • registration (login?) not working with Django devel server (runserver). Is "@" from email a problem with curl when using multipart?
                  • do not use Content-Type: multipart/form-data:
                    • #response=$(curl -X POST -H 'Content-Type: multipart/form-data' -F username=$username -F password1=$password -F password2=$password -F email=$email ${backend_url}/rest-auth/registration/ )
                  • do use Content-Type: application/json:
                    • response=$(curl -X POST -H 'Content-Type: application/json' --data-binary "{\"username\":\"$username\",\"email\":\"$email\",\"password1\":\"$password\",\"password2\":\"$password\"}" ${backend_url}/rest-auth/registration/ )

          • Identity providers
    • Autenticació amb django-rest-framework / Authentication with django-rest-framework
      • how to register users in django-rest-framework?
      • djangorestframework
        • settings.py

          • REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES']
            authentication is needed for everything
            'rest_framework.permissions.IsAuthenticated'
            authentication is needed when changing values
            'rest_framework.permissions.IsAuthenticatedOrReadOnly'

        • ViewSet
          • class ParticipantChallengeViewSet(viewsets.ModelViewSet):
                ...
                permission_classes = (permissions.IsAuthenticated,)

  • Recupera i utilitza l'usuari des de la petició / Get and use user from request

    • admin
      djangorestframework

      admin.py
      views.py
      serializers.py
      input
      @admin.register(MyModel)
      class MyModelAdmin(admin.ModelAdmin):
          exclude = ('owner',)

          # only
      objects that belong to user
          def has_change_permission(self, request, obj=None):
              has_class_permission = super(MyModelAdmin, self).has_change_permission(request, obj)
              if not has_class_permission:
                  return False
              if obj is not None and not request.user.is_superuser and request.user.id != obj.owner.id:
                  return False
              return True

          # automatically get the owner from request user
          def save_model(self, request, obj, form, change):
              if not change:
                  obj.owner = request.user
              obj.save()


      from rest_framework import permissions

      class MyModelViewSet(viewsets.ModelViewSet):
          queryset = MyModel.objects.all()
          serializer_class = MyModelSerializer
          permission_classes = (permissions.IsAuthenticated,)
         
         
      # automatically get the owner from request user
          # (pre_save has been deprecated in DRF 3.x)
          def perform_create(self, serializer):
              # Include the owner attribute directly, rather than from request data.
              instance = serializer.save(owner=self.request.user)
      class MyModelSerializer(serializers.ModelSerializer):
          def __init__(self, *args, **kwargs):
              super(MyModelSerializer, self).__init__(*args, **kwargs)
             
              if kwargs.get('context', None):
                  request = kwargs['context'].get('request', None)
                  self.user = request.user


      # Note: Nested serializer does not receive view context #2555
      class ChildSerializer(serializers.Serializer):
          ...

      class ParentSerializer(serializers.Serializer):
          # child =
      ChildSerializer(many=True)
          def __init__(self, *args, **kwargs):
              super(
      ParentSerializer,self).__init__(*args, **kwargs)
              self.fields['child'] = ChildSerializer(many=True,context=self.context)


      Consider also using a filter: use custom filter backend

      output
      @admin.register(MyModel)
      class MyModelAdmin(admin.ModelAdmin):
          exclude = ('owner',)

          # only objects that belong to user

         
      # Django < 1.6 (for 1.6 use get_queryset instead)
         
      #def queryset(self, request):
         
      #    if request.user.is_superuser:
         
      #        return MyModel.objects.all()
         
      #    return MyModel.objects.filter(usuari=request.user)
         
          # Django 1.6
      : queryset has been renamed to get_queryset
          def get_queryset(self, request):
              qs = super(MyModelAdmin, self).get_queryset(request)
              if request.user.is_superuser:
                  return qs
              return qs.filter(owner=request.user)



      from rest_framework import permissions

      class MyModelViewSet(viewsets.ModelViewSet):
         
      queryset = MyModel.objects.all()
          serializer_class = MyModelSerializer
          permission_classes = (permissions.IsAuthenticated,)
            
          # only objects that belong to user
          def get_queryset(self):

              return MyModel.objects.filter(owner=self.request.user)


  • Exemple 1 (inside directory my_app)
    • urls.py
      •     url(r'^nou_usuari/$', 'nou_usuari'),
            url(r'^entrada/$', 'entrada'),
            url(r'^zona_privada/$', 'zona_privada'),
            url(r'^sortida/$', 'sortida'),
    • views.py (Working with forms), using django.contrib.auth.forms:
      • from django.contrib.auth.forms import UserCreationForm
        from django.contrib.auth.forms import AuthenticationForm
        from django.contrib.auth import login, authenticate, logout
        from django.contrib.auth.decorators import login_required
        from django.template import RequestContext
      • def nou_usuari(request):
            if request.method=='POST':
                formulari = UserCreationForm(request.POST)
                if formulari.is_valid:
                    formulari.save()
                    return HttpResponseRedirect('/')
            else:
                formulari = UserCreationForm()
            return render_to_response('nou_usuari.html',{'formulari':formulari}, context_instance=RequestContext(request))
      • def entrada(request):
            if not request.user.is_anonymous():
                return HttpResponseRedirect('/zona_privada')
            if request.method == 'POST':
                formulari = AuthenticationForm(request.POST)
                if formulari.is_valid:
                    usuari = request.POST['username']
                    contrasenya = request.POST['password']
                    compte = authenticate(username=usuari, password=contrasenya)
                    if acces is not None:
                        if compte.is_active:
                            login(request,compte)
                            return HttpResponseRedirect('/zona_privada')
                        else:
                            return render_to_response('compte_no_actiu.html', context_instance=RequestContext(request))
                    else:
                        return render_to_response('usuari_incorrecte.html', context_instance=RequestContext(request))
            else:
                formulari = AuthenticationForm()
            return render_to_response('entrada.html',{'formulari':formulari}, context_instance=RequestContext(request))
      • @login_required(login_url='/entrada')
        def zona_privada(request):
            usuari = request.user
            return render_to_response('zona_privada.html', {'usuari':usuari}, context_instance=RequestContext(request))
      • @login_required(login_url='/entrada')
        def sortida(request):
            logout(request)
            return HttpResponseRedirect('/')
    • templates/
      • nou_usuari.html
        • <h1>Registra nou usuari</h1>
          <form id='formulari' method='post' action=''>
              {% csrf_token %}
              <table border=1>{{formulari}}</table>
              <p><input type='submit' value='Crea'/></p>
          </form>
      • compte_no_actiu.html
        • <h1>Aquest compte no està actiu</h1>
      • usuari_incorrecte.html
        • <h1>Usuari incorrecte</h1>
      • entrada.html
        • <h1>Entrada</h1>
          <form id='formulari' method='post' action=''>
              {% csrf_token %}
              <table border=1>{{formulari}}</table>
              <p><input type='submit' value='Entrada'/></p>
          </form>
      • zona_privada.html
        • <p>Benvingut {{usuari.username|upper}}
          <p>Membre des de {{usuari.date_joined}}
  • Exemple 2 (root directory)
    • urls.py (using django.contrib.auth.views)
      • import django.contrib.auth.views
        ...
        url(r'accounts/login/$', 'django.contrib.auth.views.login'),
        ...

    • my_app/templates/
    • si funciona, anirà a accounts/profile (LOGIN_REDIRECT_URL)
  • Exemple 2 (root directory)
    • urls.py (using django.contrib.auth.views)
      • import django.contrib.auth.views
        ...
        url(r'accounts/login/$', 'django.contrib.auth.views.login',  {'template_name': 'login2.html'}),
        ...

    • my_app/templates/
    • si funciona, anirà a accounts/profile

Admin

  • The Django admin site (1.6) (1.7)
  • Django admin snippets
  • Polls example admin
  • Registre / Register
    • admin.site.register( TotoModel )
    • Django >= 1.7
      • @admin.register(TotoModel)
        class TotoModelAdmin(admin.ModelAdmin):
            ## list

            # default:
            #list_display = ('__unicode__',)

            # non-default:
            list_display = ('field_1', 'field_2', 'date_field_10',)

            # filter by fields (filter area on right):
            list_filter = ('field_1',)

            # searchable fields (search on top):
            search_fields = ('field_2',)

            # dates (on top):
            date_hierarchy = 'date_field_10'


            ## detail

            # non-grouped fields:
            #fields = ('field_1','field_2',)

            # fields grouped in a single line:
            #fields = ['
        field_1','field_2', ('field_3', 'field_4'), ('field_5', 'field_6'), 'field_7']

            # fields grouped:
            fieldsets = [
                (None,         {'fields': ['
        field_1', 'field_2'], 'classes': ['wide']}),
                (_('Group A'), {'fields': ['
        field_3', 'field_4'], 'classes': ['wide']}),
                (_('Group B'), {'fields': ['
        field_5', 'field_6'], 'classes': ['wide']}),
                (None,         {'fields': ['
        field_7'], 'classes': ['wide']}),
            ]


            # prepopulate SlugFields:
            prepopulated_fields = {"slug_field_1": ("field_1",)}

            # non-editable fields:
            readonly_fields = ('field_3',)

            # exluded fields (when not specifying fields):
            #exclude = ('field_8',)

            # many2many:
            filter_horizontal = ('m2m_field_20',)
           
        filter_vertical = ('m2m_field_21',)

            # foreign keys to another model (can perform search in a pop-up window):
            raw_id_fields = ('fk_field_20',)

            # foreign keys from another model:
            inlines = [ModelAInline,ModelBInline,]
            ...
    • Django < 1.7
      • class TotoModelAdmin(admin.ModelAdmin):
            ...
        admin.site.register( TotoModel, TotoModelAdmin )
  • Desregistre / Unregister
    • my_project/urls.py
      • from django.contrib import admin
        from django.contrib.auth.models import User, Group

        admin.site.unregister(User)
        admin.site.unregister(Group)

  • Llista / List
  • Detall / Detail
    • Groups
      • no groups
        • fields = ['first','second', 'third_first', 'third_second', 'fourth_first', 'fourth_second', 'fifth']
      • in a single line
        • fields = ['first','second', ('third_first', 'third_second'), ('fourth_first', 'fourth_second'), 'fifth']
      • in a section
        • fieldsets = [
                   (None,        {'fields': ['first','second'], 'classes': ['wide']}),
                   (_('Third'),  {'fields': ['third_first', 'third_second'], 'classes': ['wide']}),
                   (_('Fourth'), {'fields': ['fourth_first', 'fourth_second'], 'classes': ['wide']}),
                   (None,        {'fields': ['fifth'], 'classes': ['wide']}),
               ]
    • Inline
      • Simple examples
        • admin.py
          • from django.contrib import admin

            #class MembershipInline(admin.StackedInline):
            class MembershipInline(admin.TabularInline):
                model = Membership

            @admin.register(Group)
            class GroupAdmin(admin.ModelAdmin):
                model = Group
                inlines = [MembershipInline,]

      • How to limit django admin inline formsets
      • How do I require an inline in the Django Admin?
        • forms.py
        • admin.py
          • class TotoInline(admin.StackedInline)
                ...
                formset = RequiredInlineFormSet

      • exactly 2 objects:
        • class TotoInline(admin.StackedInline)
              ...
              max_num = 2
              extra = 2
      • Conditional inlines
        • Django admin inline conditional values
        • Filtering an inline's dropdown based on the inline instance
        • with two-step:
          • @admin.register(Stream)
            class StreamAdmin(admin.ModelAdmin):
                model = Stream
               
                def get_inline_instances(self, request, obj=None):
                   
            inlines = []
                    if obj!=None:
                        if (obj.stream_type == 2) or (obj.stream_type == 27): # video: 2, 27
                            inlines = [VideoStreamInline]
                        elif (obj.stream_type == 4) or (obj.stream_type == 17): # audio: 4, 17
                            inlines = [AudioStreamInline]
                    return [inline(self.model, self.admin_site) for inline in inlines]


                def response_add(self, request, obj, post_url_continue=None):
                    if '_addanother' not in request.POST and '_popup' not in request.POST:
                        request.POST['_continue'] = 1
                    return super(StreamAdmin, self).response_add(request, obj, post_url_continue)

      • Nested inlines
        • django-nested-inline
          • pip install -e git+git://github.com/s-block/django-nested-inline.git#egg=django-nested-inline
          • bugs:
            • in order to use overwritten get_inline_instances (see above: conditional inlines), the following files has to be modified:
              • <virtualenv_path>/src/django-nested-inline/nested_inline/static/admin/js/inlines-nested.js (to avoid bug of having the same inline as the first element, when doing "Add another...")
                • ??
            • not working with validate_unique
      • Problemes / Problems
  • Delete
  • Two-step (as in user creation: edit the just added object)
    • In Django, how do I mimic the two-step method of adding users through the admin for my own models?
    • class StreamAdmin(admin.ModelAdmin):
          # fieldsets for first step (add new object)
          add_fieldsets = (
              (None, {
                  'classes': ('wide',),
                  'fields': ('username', 'password1', 'password2'),
              }),
          )

          # fieldsets for second step in add object; default for modifying an object
          fieldsets = (
              (None, {'fields': ('username', 'password')}),
              (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
              (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                             'groups', 'user_permissions')}),
              (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
          )

          def get_fieldsets(self, request, obj=None):
              if not obj:
                  return self.add_fieldsets
              return super(UserAdmin, self).get_fieldsets(request, obj)

          def response_add(self, request, obj, post_url_continue=None):
              if '_addanother' not in request.POST and '_popup' not in request.POST:
                  request.POST['_continue'] = 1
              return super(StreamAdmin, self).response_add(request, obj, post_url_continue)
  • Extensions
    • How to add report section to the Django admin?
    • Three awesome Django admin enhancements
    • Bulk edit
      • django-mass-edit
        • Instal·lació / Installation
          • pip install django-mass-edit
        • Configuració / Setup
          • my_project/my_project/settings.py
            • INSTALLED_APPS = (
                  ...
                  'massadmin',
                  ...

          • my_project/my_project/urls.py
            • urlpatterns = [
                  url(r'^admin/', include(admin.site.urls)),
                  url(r'^admin/', include('massadmin.urls')),
              ...
        • Usage
          • Action: Massively edit
    • django-adminplus
      • Instal·lació / Installation
        • pip install django-adminplus
        • pip install -e git://github.com/jsocol/django-adminplus#egg=django-adminplus
      • Utilització / Usage
        • my_project/my_project/settings.py
          • INSTALLED_APPS = (
                #'django.contrib.admin',
                'django.contrib.admin.apps.SimpleAdminConfig',
                'adminplus',
            )

        • my_project/urls.py
          • from adminplus.sites import AdminSitePlus
            admin.site = AdminSitePlus()

            admin.autodiscover()
        • my_project/my_app/admin.py
          • @admin.site.register_view('my_view',_("View to do something"))
            def my_view(request, *args, **kwargs):
                # do something
               
                # redirect to admin home
                url = reverse('admin:index')
                return HttpResponseRedirect(url)

      • Problemes / Problems
        • Could not parse the remainder: ':index' from 'admin:index'. The syntax of 'url' changed in Django 1.5, see the docs.
          • Solució / Solution:
            • modify file /your_python_path_or_virtualenv//src/django-adminplus/adminplus/templates/adminplus/base.html
              • <a href="{% url 'admin:index' %}">Home</a> &rsaquo; {{ title }}
    • Grappelli
    • Adminactions
    • import-export
    • CKEditor
  • Personalizació / Customization
    • AdminSite (1.7)
      • my_project/urls.py
        • from django.utils.translation import ugettext_lazy as _

          admin.site.site_header = _('My site_header') # "Django administration"
          admin.site.site_title =
          _('My site_title') # "Django site admin"
          admin.site.index_title =
          _('My index_title') # "Site administration"
    • Custom ordering
    • Overriding admin templates (1.7)
    • AdminSite attributes (dev version)
    • Customizing the Django Admin (slides)
    • Exemple d'admin
      • Customize the admin look and feel
      • Customize the admin index page
      • Només un fitxer:
        • mkdir -p my_project/templates/admin
        • cp -pr /opt/PYTHON27/lib/python2.7/site-packages/django/contrib/admin/templates/admin/base.html my_project/templates/admin
      • Tota l'estructura de fitxers:
        • cp -pr /opt/PYTHON27/lib/python2.7/site-packages/django/contrib/admin/templates my_project/templates
      • modifiqueu, per exemple, base.html
      • settings.py
        • TEMPLATE_DIRS
          • "if it grew more sophisticated and required modification of Django’s standard admin templates for some of its functionality, it would be more sensible to modify the application’s templates, rather than those in the project."
          • TEMPLATE_DIRS = (
                # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
                # Always use forward slashes, even on Windows.
                # Don't forget to use absolute paths, not relative paths.
                os.path.join(BASE_DIR, 'templates'),
            )

        • TEMPLATE_LOADER
      • Botó / Button
        • Element de la llista / Element in a list
          • How to Add Custom Action Buttons to Django Admin
          • Afegir un botó a cada element de la llista, que retorna un json amb la serialització d'unes dades addicionals / Add a button to every item in the list, that returns a json with the serialization of additional data:
            • admin.py
              from django.http import HttpResponse
              from django.utils.html import format_html

              from myapp.serializers import AdditionalSerializer

              @admin.register(MyModel)
              class MyModelAdmin(admin.ModelAdmin):
                  # list
                  list_display = (...,'additional_buttons',...)
                  def get_model_info(self):
                      # module_name is renamed to model_name in Django 1.8
                      app_label = self.model._meta.app_label
                      try:
                          return (app_label, self.model._meta.model_name,)
                      except AttributeError:
                          return (app_label, self.model._meta.module_name,)

                  def get_urls(self):
                      urls = super(MyModelAdmin, self).get_urls()
                      info = self.get_model_info()
                      my_urls = [
                          url(r'^(?P<uuid>.+)/additional/$', self.admin_site.admin_view(self.additional_json_view), name='%s_%s_additional' % info)
                      ]
                      return my_urls + urls
                 
                  def additional_buttons(self, obj):
                      return format_html(
                          '<a class="button" href="{}">Additional</a>',
                          reverse('admin:myapp_mymodel_additional', args=[obj.uuid])
                      )
                  additional_buttons.short_description = 'Additional'
                  additional_buttons.allow_tags = True


                  def additional_json_view(self, request, uuid, *args, **kwargs):
                     
              #instance = self.get_object(request, pk)
                      # to use uuid field instead of pk to get instance:
                      instance = self.get_object(request, uuid, from_field='uuid')
                     
                      # build temporary dict
                      obj = dict()
                      obj['my_key'] = instance.get_my_value()
                      ...

                      # serialize dict
                      serializer = AdditionalSerializer(obj) 
                       
                      return HttpResponse(json.dumps(serializer.data), content_type="application/json")


        • General a la llista / General in the list
          • Afegir un botó a la llista, per a pujar un fitxer, parsejar-lo i desar-ne els objectes (només una pàgina addicional, amb formulari) / Add a button to upload a file, parse it and save the objects:
            • admin.py
              forms.py
              my_app/templates/admin/my_app/
              from django.contrib import messages
              from my_app.actions import
              parse_mymodel_uploaded_file
              from django.template.response import TemplateResponse

              @admin.register(MyModel)
              class MyModelAdmin(admin.ModelAdmin):
                  model = MyModel

                  change_list_template = 'admin/my_app/change_list_import_template.html'

              change_list_import_template.html
              • {% extends "admin/change_list.html" %}
                {% load i18n %}

                {% block object-tools-items %}
                  <li><a href="import_view/">{% trans "Import" %}</a></li>
                  {{ block.super }}
                {% endblock %}
                  def get_model_info(self):
                      # module_name is renamed to model_name in Django 1.8
                      app_label = self.model._meta.app_label
                      try:
                          return (app_label, self.model._meta.model_name,)
                      except AttributeError:
                          return (app_label, self.model._meta.module_name,)

                  def
              get_urls(self):
                      urls = super(MyModelAdmin, self).get_urls()
                      info = self.get_model_info()
                      my_urls = patterns('',
                          url(r'^import_view/$', self.admin_site.admin_view(self.import_view), name='%s_%s_import_view' % info)
                      )
                      return my_urls + urls


                  def import_view(self, request, *args, **kwargs):
                      # custom view which should return an HttpResponse
                     
                      # if a GET (or any other method) we'll create a blank form
                      if request.method != 'POST':
                          formulari = ImportForm()

                      # if this is a POST request we need to process the form data
                      else:
                          formulari = ImportForm(request.POST, request.FILES)
                          if formulari.is_valid():
                              # do something:
                              errors = parse_mymodel_uploaded_file(request.FILES['import_file'], request.POST['input_format'])
                             
                              if errors is not None:
                                  self.message_user(request, "Errors: %s" % errors, level=messages.ERROR)
                              else:
                                  self.message_user(request, "Successful import")

                              url = reverse('admin:%s_%s_changelist' % self.get_model_info(),
                                            current_app=self.admin_site.name)
                              return HttpResponseRedirect(url)
                     
                      context = {}
                      context['opts'] = self.model._meta
                      context['formulari'] = formulari
                      return TemplateResponse(request, 'admin/broadcast/import_template.html',
                                              context, current_app=self.admin_site.name)

              from django import forms
              from django.utils.translation import ugettext_lazy as _

              class ImportForm(forms.Form):
                  import_file = forms.FileField(label=_('File to import'))
                  input_format = forms.ChoiceField(
                          label=_('Format'),
                          choices=(('auto','(auto)'),
                                   ('text/csv','csv'),
                                   ('application/vnd.ms-excel','xls'),
                                   ('text/xml','xml'),
                                   ('application/json','json'),
                                   ),
                          )

              import_template.html
              • {% extends "admin/base_myproject.html" %}
                {% load url from future %}
                {% load i18n %}
                {% load admin_urls %}

                {% block breadcrumbs_last %}
                {% trans "Import" %}
                {% endblock %}


                {% debug %}

                {% block content %}
                <h1>{% trans "Import" %}</h1>

                {% trans "Import from file:" %}
                <form action="{{ form_url }}" method="post" enctype="multipart/form-data">
                    {% csrf_token %}
                    {{ formulari }}
                       <div class="submit-row">
                      <input type="submit" class="default" name="confirm" value="{% trans "Import" %}">
                    </div>
                   
                </form>
                  {{ block.super }}
                {% endblock %}


            • my_project/templates/admin/base_myproject.html
              • {% extends "admin/base_site.html" %}
                {% load i18n admin_static admin_modify %}
                {% load admin_urls %}
                {% load url from future %}

                {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}
                {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
                {% if not is_popup %}
                {% block breadcrumbs %}
                <div class="breadcrumbs">
                <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
                &rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_label|capfirst|escape }}</a>
                &rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
                &rsaquo; {% block breadcrumbs_last %}{% endblock %}
                </div>
                {% endblock %}
                {% endif %}
            • actions.py
              • from django.core.files import File
                from rest_framework.compat import BytesIO
                from rest_framework.parsers import JSONParser

                def parse_mymodel_uploaded_file(uploaded_file, content-type):
                    print 'content_type: %s' % uploaded_file.content_type
                    errors = dict()
                   
                    # get the content-type from the uploaded file
                    if content_type == 'auto':
                        content_type = uploaded_file.content_type

                    if content_type == 'application/json':
                        fitxer = File(uploaded_file)
                        content = fitxer.read()
                        stream = BytesIO(content)
                        data = JSONParser().parse(stream)
                        fitxer.close()

                        serializer = SimpleEventSerializer(data=data, many=True)
                        if serializer.is_valid():
                            serializer.save(force_insert=True)
                  
                    else:
                        print "No parser for content type: %s" % (content_type)       
                        errors['error'] =  "No parser for content type: %s" % (content_type)

                    return errors
          • Afegir un botó a la llista (pàgina addicional i pàgina de confirmació, sense formularis)
            admin.py
            my_app/templates/admin/my_app/
            from django.contrib import admin, messages

            @admin.register(MyModel)
            class MyModelAdmin(admin.ModelAdmin):
                model = MyModel

                change_list_template = 'admin/my_app/change_list_apply_template.html'
            change_list_apply_template.html
            • {% extends "admin/change_list.html" %}
              {% load i18n %}

              {% block object-tools-items %}
                <li><a href="apply_view/">{% trans "Apply" %}</a></li>
                {{ block.super }}
              {% endblock %}



                def get_model_info(self):
                    # module_name is renamed to model_name in Django 1.8
                    app_label = self.model._meta.app_label
                    try:
                        return (app_label, self.model._meta.model_name,)
                    except AttributeError:
                        return (app_label, self.model._meta.module_name,)

                def
            get_urls(self):
                    urls = super(General_ConfigurationAdmin, self).get_urls()
                    info = self.get_model_info()       
                    my_urls = patterns('',
                        url(r'^apply_view/$', self.admin_site.admin_view(self.apply_view),
                            name='%s_%s_apply_view' % info),
                        url(r'^confirmed_view/$', self.admin_site.admin_view(self.confirmed_view),
                            name='%s_%s_confirmed_view' % info),                        
                    )
                    return my_urls + urls



                def apply_view(self, request, *args, **kwargs):
                    # custom view which should return an HttpResponse
                   
                    active_objects = MyModel.objects.filter(is_active=True)
                           
                    context = {}
                    context['opts'] = self.model._meta
                    context['active_objects'] = active_objects

                    return TemplateResponse(request, 'admin/my_app/apply_template.html',
                                            context, current_app=self.admin_site.name)

            apply_template.html
            • {% extends "admin/base_myproject.html" %}
              {% load i18n %}
              {% load admin_urls %}
              {% load url from future %}

              {% debug %}
              {% block breadcrumbs_last %}
              {% trans "Apply" %}
              {% endblock %}

              {% block content %}
              <h1>{% trans "Apply" %}</h1>

              {% trans "The following objects are active:" %}

              {% if active_objects %}
                <ul>
                {% for active_object in active_objects %}
                <li>{{active_object.Name}}</li>
                {% endfor %}
                </ul>
              {% else %}
              {% endif %}
                
              <form action="{% url opts|admin_urlname:"confirmed_view" %}" method="POST">
                {% csrf_token %}
                <div class="submit-row">
                  <input type="submit" class="default" name="confirm" value="{% trans "Confirm" %}">
                </div>
              </form>

              {{ block.super }}
              {% endblock %}


                def confirmed_view(self, request, *args, **kwargs):
                    # do something:
                    do_something_with_some_objects()
                   
                    success_message = _('Successfully applied')
                    messages.success(request, success_message)

                    url = reverse('admin:%s_%s_changelist' % self.get_model_info(),
                                  current_app=self.admin_site.name)
                    return HttpResponseRedirect(url)


      • Afegir una acció per a exportar / Add an action to export
        • actions.py
          • import os
            from datetime import datetime
            from django.conf import settings
            from django.core.files import File

            from rest_framework.renderers import JSONRenderer

            def export_mymodel_json(queryset)
                serializer = MyModelSerializer(queryset, many=True)
                content = JSONRenderer().render(serializer.data, renderer_context={'indent':4})

                now = datetime.now()  
                filename = os.path.join(settings.BASE_DIR, "mymodel_%s.json" % ( now.strftime('%Y%m%d-%H%M%S') ))

                f = open(filename,mode='w')
                fitxer =
            File(f)
                fitxer.write(content)
                fitxer.close
               
                return filename

        • admin.py
          • from django.conf.urls import patterns
            from my_app.actions import export_mymodel_json, parse_mymodel_uploaded_file

            @admin.register(MyModel)
            class MyModelAdmin(admin.ModelAdmin):
                model = MyModel
               
                # add action to export to a local file
                actions = ['export_json_from_admin']
               
                def export_mymodel_json_from_admin(self, request, queryset):
                    filename = export_mymodel_json(queryset)
                    self.message_user(request, "Successfully written file: %s" % filename)
                export_mymodel_json_from_admin.short_description = _("Export as JSON to a local file")

    • Django Admin - change header 'Django administration' text
  • Admin actions
    • Example dealing with exceptions:
      • models.py
        • class MyModel(models.Model):
              def do_something(self):
                  if something_went_wrong:
                      raise Exception("reason is ...")

      • admin.py
        • from django.contrib import admin
          from django.utils.translation import ugettext_lazy as _
          from django.contrib import messages

          from my_app.models import MyModel

          @admin.register(Instance)
          class MyModelAdmin(NestedModelAdmin):
              model = MyModel
              actions = ['first_action_from_admin']

              def first_action_from_admin(self, request, queryset):
                  for instance in queryset:
                      try:
                          instance.do_something()
                          self.message_user(request, 'Successfully done something with object: %s' % instance.name)
                      except Exception as e:
                          self.message_user(request, 'Action could not be perfomed with instance %s (%s)' % (instance.__unicode__, e), level=messages.ERROR)
              first_action_from_admin.short_description = _("Do something with selected instances")   

    • Example: write the result of a serialization to a file
      • actions.py
        • import os
          from datetime import datetime
          from rest_framework.renderers import JSONRenderer
          from django.core.files import File
          from my_app.serializers import
          MyModelSerializer

          def generate_json_file(queryset):
              serializer = MyModelSerializer(queryset, many=True)
              content = JSONRenderer().render(serializer.data, renderer_context={'indent':4})

              now = datetime.now()
              filename = os.path.join(settings.BASE_DIR, "nits_%s.json" % ( now.strftime('%Y%m%d-%H%M%S') ))
             
              f = open('contingut.json',mode='w')
              fitxer = File(f)
              fitxer.write(content)
              fitxer.close

              return filename
      • admin.py
        • from my_app.actions import generate_json_file
          from django.utils.translation import ugettext_lazy as _


          @admin.register(MyModel)
          class MyModelAdmin(admin.ModelAdmin):
              model = MyModel
              ...
             
              actions = ['generate_json_file_from_admin']

              def generate_json_file_from_admin(self, request, queryset):
                  filename = generate_json_file(queryset)
                  self.message_user(request, 'Successfully written file: %s' % (filename))
              generate_json_file_from_admin.short_description = _("Generate JSON file")
    • Example with a common function:
      • models.py
        • class MyModel1(models.Model):
              def do_something(self):
                  if something_went_wrong:
                      raise Exception("reason is ...")

          class MyModel2(models.Model):
              def do_something(self):
                  if something_went_wrong:
                      raise Exception("reason is ...")

      • admin.py
        • def common_action_from_admin(modeladmin, request, queryset):
              for instance in queryset:
                  try:
                      instance.do_something()
                      modeladmin.message_user(request, 'Successfully performed common action for: %s' % instance.__unicode__())
                  except Exception as e:
                      modeladmin.message_user(request, 'Could not perform action for %s (%s)' % (instance.__unicode__(), e), level=messages.ERROR)
             
          common_action_from_admin.short_description = _("Perform action")

          @admin.register(MyModel1)
          class MyModelAdmin(admin.ModelAdmin):
              model = MyModel1
              ...
              actions = [common_action_from_admin]

          @admin.register(MyModel2)
          class MyModelAdmin(admin.ModelAdmin):
              model = MyModel2
              ...
              actions = [common_action_from_admin]
    • Missatges després de l'acció / Messages after the action
    • Personalització d'accions / Action personalization
      • Conditionally enabling or disabling actions
      • Django admin snippets
      • Esborra / Delete
        • The default “delete selected” admin action in Django
        • Canvi de nom / Change name
          • ...py
            • from django.contrib.admin.actions import delete_selected
              delete_selected.short_description = u'Remove selected items'

        • Deshabilita globalment i habilita localment / Globally disable and localy enable
          • urls.py
            • admin.site.disable_action('delete_selected')
          • my_app/admin.py
            • class FooAdmin(admin.ModelAdmin):
                  actions = ['my_action', 'my_other_action', admin.actions.delete_selected]
        • Deshabilita localment / Localy enable
          • my_app/admin.py
            • class MyModelAdmin(admin.ModelAdmin):
                  ...

                  def get_actions(self, request):
                      actions = super(MyModelAdmin, self).get_actions(request)
                      if 'delete_selected' in actions:
                          del actions['delete_selected']
                      return actions

      • admin.py
        • class TotoAdmin(admin.ModelAdmin):
              # filter available actions by user
              def get_actions(self, request):
                  actions = super(TotoAdmin, self).get_actions(request)
                  if not request.user.is_superuser:
                      if '' in actions:
                          del actions['first_admin_action']
                      if 'second_admin_action' in actions:
                          del actions['second_admin_action']
                      if 'third_admin_action' in actions:
                          del actions['third_admin_action']
                      except KeyError:
                          pass
                  return actions
  • Usuaris / Users
    • Users and the admin
    • models.py
      • class Toto(model.Model):
            title = models.CharField(max_length=250)
            slug = models.SlugField()
            usuari = models.ForeignKey(User, related_name='totos')
            ...

    • admin.py (Mostra només els objectes de l'usuari / Show only objects of user)
      • class TotoAdmin(admin.ModelAdmin):
            exclude = ('usuari',)
            prepopulated_fields = {"slug": ("title",)}
            readonly_fields = ('non_editable_toto_field',)

            # only owned objects
            def has_change_permission(self, request, obj=None):
                has_class_permission = super(TotoAdmin, self).has_change_permission(request, obj)
                if not has_class_permission:
                    return False
                if obj is not None and not request.user.is_superuser and request.user.id != obj.usuari.id:
                    return False
                return True
           
        # Django < 1.6 (for 1.6 use get_queryset instead)
           
        def queryset(self, request):
                if request.user.is_superuser:
                    return Toto.objects.all()
                return Toto.objects.filter(usuari=request.user)
            # Django 1.6
        : queryset has been renamed to get_queryset
            def get_queryset(self, request):
                qs = super(TotoAdmin, self).get_queryset(request)
                if request.user.is_superuser:
                    return qs
                return qs.filter(usuari=request.user)


            # automatically get the user from request
            def save_model(self, request, obj, form, change):
                if not change:
                    obj.usuari = request.user
                obj.save()


        admin.site.register(Toto, TotoAdmin)
    • Overwrite User unicode
      • admin.py
        • def user_unicode(self):
              string = u'{}'.format(self.username)
              return  string

          User.__unicode__ = user_unicode


          admin.site.unregister(User)
          admin.site.register(User)

  • Accions recents / Recent actions (log entries)
    • View all log entries in the admin 2
      • admin.py
        • ...
          class LogEntryAdmin(admin.ModelAdmin):
           
              date_hierarchy = 'action_time'
             
              # Django <=1.8
              #readonly_fields = LogEntry._meta.get_all_field_names()
              # Django >=1.11 (?)
              readonly_fields = [f.name for f in LogEntry._meta.get_fields()]
          ...


  • ManyToManyField
    • Working with many-to-many models (Inline)
    • Access manytomany from the model that does define it:
      • models.py
        • class Person(models.Model):
               name = models.CharField(max_length=128)
               def __unicode__(self):
                   return self.name

          class Classroom(models.Model):
               name = models.CharField(max_length=128)
               members = models.ManyToManyField(Person, related_name='classrooms')
               def __unicode__(self):
                   return self.name
      • admin.py
        • class ClassroomAdmin(admin.ModelAdmin):
             
          ...
              # by default, 'members' is added as a regular field.
              ...
              # if you want some different presentation:
              #
          filter_horizontal = ('members',)
              #
          filter_vertical = ('members',)
          admin.site.register(Classroom, ClassroomAdmin)
    • Access manytomany with through (explicit or implicit):
      • inline of it
      • implicit through
        • models.py
          • class Person(models.Model):
                name = models.CharField(max_length=128)

            class Classroom(models.Model):
                name = models.CharField(max_length=128)
                members = models.ManyToManyField(Person, related_name='classrooms')
        • admin.py
          • class ClassroomPersonInline(admin.TabularInline)
                model = Classroom.members.through

            class ClassroomAdmin(admin.ModelAdmin):
               
            ...
                inlines = [ClassroomPersonInline,]
            admin.site.register(Classroom, ClassroomAdmin)
      • explicit through
        • ...
    • Horizontal or vertical interface (two boxes)
    • Access manytomany from the model that does not define it:
      • option 1: use through (every m2m relation has it, even if not explicitly declared) and inline of it
        • models.py
          • class Person(models.Model):
                 name = models.CharField(max_length=128)
                 def __unicode__(self):
                     return self.name

            class Classroom(models.Model):
                 name = models.CharField(max_length=128)
                 members = models.ManyToManyField(Person, related_name='classrooms')
                 def __unicode__(self):
                     return self.name

        • admin.py
          • class ClassroomInline(admin.TabularInline):
                model = Classroom.members.through
                extra = 0

            class PersonAdmin(admin.ModelAdmin):
                ...
                inlines = [
            ClassroomInline,]
            admin.site.register(Person, PersonAdmin)
      • option 2: horizontal_filter
        • Django admin interface: using horizontal_filter with inline ManyToMany field
        • admin.py
          • from django import forms
            from django.contrib.admin.widgets import FilteredSelectMultiple
            from django.contrib.auth.models import User,Group

            class CustomGroupAdminForm(forms.ModelForm):
                class Meta:
                    model = Group
                    fields = ('name','permissions',)

                users = forms.ModelMultipleChoiceField(
                    queryset=User.objects.all(),
                    required=False,
                    widget=FilteredSelectMultiple(
                        verbose_name='Users',
                        is_stacked=False
                    )
                )

                def __init__(self, *args, **kwargs):
                    super(CustomGroupAdminForm, self).__init__(*args, **kwargs)
                    if self.instance.pk:
                        self.fields['users'].initial = self.instance.user_set.all()

                def save(self, commit=True):
                    group = super(CustomGroupAdminForm, self).save(commit=False) 
                    if commit:
                        group.save()

                    if group.pk:
                        group.user_set = self.cleaned_data['users']
                        self.save_m2m()

                    return group

            class CustomGroupAdmin(GroupAdmin):
                form = CustomGroupAdminForm

            admin.site.unregister(Group)
            admin.site.register(Group, CustomGroupAdmin)
    • Display choices as checkbox
    • Filter choices
      • class MyModelAdmin(admin.ModelAdmin):
            model = MyModel

            def formfield_for_manytomany(self, db_field, request, **kwargs):
                if db_field.name == "cars":
                    kwargs["queryset"] = Car.objects.filter(carType=CAR_TYPE_COUPE)
                return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
      • class MyModelAdmin(admin.ModelAdmin):
            model = MyModel

            def formfield_for_manytomany(self, db_field, request, **kwargs):
                if db_field.name == "cars":
                    # get parent_id
                    parent_obj_id = request.resolver_match.args[0]
                    # only objects whose parent is the present one
                    kwargs["queryset"] = Car.objects.filter(parent_field__id=
        parent_obj_id)
                return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
    • if many2many objects are added by code (e.g. in a post_save receiver: my_instance.my_related_objects.add(my_related_instance) ), the relations must be explicitly added:
      • admin.py
        • class MyModelAdmin(admin.ModelAdmin):
              def save_related(self, request, form, formsets, change):
                  super(MyModelAdmin, self).save_related(request, form, formsets, change)

              def save_model(self, request, obj, form, change):
                  try:
                      super(MyModelAdmin, self).save_model(request, obj, form, change)

                      # add your m2m here, so as they are saved in save_related:
                      if obj.my_related_objects:
                          form.cleaned_data['my_related_objects'] = obj.my_related_objects.all()
                  except Exception as e:
                      raise e

    • list_display
      • many-to-many in list display django
      • manytomany column, with a different name:
        • models.py
          • class MyUser(AbstractUser):
                ...
                def get_groups(self):
                    return ",".join([g.name for g in self.groups.all()])

        • admin.py
          • from my_app.models import MyUser

            class MyUserAdmin(UserAdmin):
                model = MyUser
               
                def grupets(self,item):
                    return item.get_groups()
                   
                list_display =  ('username','grupets',)

            admin.site.register(MyUser, MyUserAdmin)
  • ForeignKey
    • Can “list_display” in a Django ModelAdmin display attributes of ForeignKey fields?
    • Foreign keys in django admin list display
      • ForeignKey defined in the model
      • ForeignKey defined in another model and pointing to this model (also consider GenericForeignKey)

      • admin.py
        • class TotoAdmin()
              list_display = (..., 'name_of_foreign_key_field_url', ...)
              ...
              def name_of_foreign_key_field_url(self,item):
                  target = getattr(item, 'name_of_foreign_key_field')
                  return '<a href="../%s/%d/">%s</a>' % (target._meta.module_name, target.id, unicode(target))
              name_of_foreign_key_field_url.allow_tags = True
              name_of_foreign_key_field_url.short_description = _("Name")
      • Show related as a list, with links:
        • myapp/models.py
          • class MyModel()
                name = models.CharField(max_length=50)

            class SonModel()
                mymodel = models.ForeignKey(MyModel, related_name='sons')
                name = models.CharField(max_length=50)
        • myapp/admin.py
          • from django.contrib import admin

            @admin.register(MyModel)
            class MyModeldmin(admin.ModelAdmin):
                model = MyModel
                list_display = ('name','sons_list',)

                def sons_list(self, item):
                    sons = item.sons.all()
                    result = '<ul>'
                    for son in sons:
                        url = reverse('admin:myapp_sonmodel_change', args=(son.id,))
                        result = '%s <li><a href="%s">%s</a></li>' % (result, url, son.name)
                    result = "%s </ul>" % result
                    return result
               
            sons_list.short_description = 'Sons'
               
            sons_list.allow_tags = True
    • Filter dropdown choices
      • formfield_for_foreignkey
        • class MyModelAdmin(admin.ModelAdmin):
               def formfield_for_foreignkey(self, db_field, request, **kwargs):
                   if db_field.name == "car":
                       kwargs["queryset"] = Car.objects.filter(owner=request.user)
                   return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
        • class MyModelAdmin(admin.ModelAdmin):
              def formfield_for_foreignkey(self, db_field, request, **kwargs):
                  if db_field.name == 'car':
                      # if updating, get id for present object
                      if request.resolver_match.args:
                          object_id = request.resolver_match.args[0]
                          if object_id:
                              ...
                  return super(MixerAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

      • Prepopulate django filter horizontal in admin
      • Model limit_choices_to={'user': user}
      • Filtering Dropdown Lists in the Django Admin
      • django admin inlines: get object from formfield_for_foreignkey
        • parent_obj_id = request.resolver_match.args[0]
    • Filter when using raw_id_fields
    • Display choices as radio-button
      • class MyAdmin(admin.ModelAdmin):
            radio_fields = {"myforeignkeyfield": admin.HORIZONTAL}

  • Resizing forms
  • Problemes / Problems

I18n / l10n

  • Internationalization and localization
  • Django packages for internationalization
  • Rosetta
  • Easymode
  • Serialization
  • Unicode in Python
  • coding in file.py
    • # coding: utf-8
  • in Python files:
    • from django.utils.translation import ugettext_lazy as _
      ...
      verbose_name = _("Nom del model")

    • from django.utils import translation
      current_language = translation.get_language()
    • from django.utils import translation
      current_language = translation.get_language()
      forced_language = 'ca'
      translation.activate(forced_language)
      ...
      translation.activate(current_language)
    • Utilització amb variables / Usage with variables
      • file1.py
        • # do not use aliases; string would not appear in django.po:
          # from django.utils.translation import ugettext_lazy as _, ugettext_noop as _noop
          from django.utils.translation import ugettext_lazy as _, ugettext_noop

          my_var = ugettext_noop("My translatable string")
          my_func(my_var)

      • file2.py
        • from django.utils.translation import ugettext_lazy as _

          def my_func(cadena)
              print _(cadena)

  • in templates:
    • {% load i18n %}

      {% trans "My text" %}
  • Celery tasks
    • ...py
      • from django.utils import translation

        ...
            ctx = {
                ...
                'lang':translation.get_language(),
            }
        do_something.delay(...,ctx)

    • tasks.py
      • from django.utils import translation

        @shared_task
        def do_something_task(..., ctx):
            lang = ctx.get('lang', settings.LANGUAGE_CODE)
            translation.activate(lang)
            ...


  • Activation of i18n (user can select the language according to the browser preferences: Accept-Language / HTTP_ACCEPT_LANGUAGE):
    • project_name/settings.py (order is important)
      • MIDDLEWARE_CLASSES = (
            'django.contrib.sessions.middleware.SessionMiddleware',
            'django.middleware.locale.LocaleMiddleware',
            'django.middleware.common.CommonMiddleware',
        ...

  • Activation of i18n for project_name:
    • dependències / dependencies
      • Mageia
        • urpmi gettext
    • primera vegada / first time
      • cd project_name; mkdir locale
      • project_name/settings.py
        • Django > ...
          • LOCALE_PATHS = (
                            os.path.join(BASE_DIR, 'locale'),
                            )
        • Django < ...
          • import os
            PROJECT_PATH = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
          • LOCALE_PATHS = (
                           
            PROJECT_PATH + '/locale/',
            )
      • afegiu la traducció al català (ca) / add Catalan (ca) translation:
        • django-admin.py makemessages -l ca
      • then translate locale/ca/LC_MESSAGES/django.po (e.g. using lokalize)
        • lokalize
          • Project / New project
            • my_project/locale/index.lokalize
          • django.po
      • ./manage compilemessages
      • django-admin.py compilemessages
    • següents vegades / next times:
      • cd project_name/app_name
      • ./manage makemessages -a --ignore="env*"
      • django-admin.py makemessages -a
      • translate locale/ca/LC_MESSAGES/django.po (e.g. using lokalize)
      • django-admin.py compilemessages
  • Activation of i18n for project_name/app_name:
    • Com per a un projecte, però no cal LOCALE_PATHS / Same as above, but LOCALE_PATHS is not needed
  • Third party modules
  • Language in user profile
  • i18n of models
    • Related projects (django-modeltranslation)
    • django-modeltranslation
      • Use new meta API with django 1.8 #299
      • Utilització amb djangorestframework / Usage with djangorestframework
        • NOT NEEDED: How to handle translations when serializing?
          • TranslateSerializer
        • curl
          • curl --user admin:admin http://127.0.0.1:8000/api/toto/?format=json --header 'Accept-Language: ca'
        • override language (force language by query params, even if accept language is sent)
          • class OverrideTranslateSerializer(serializers.ModelSerializer):
                def __init__(self, *args, **kwargs):
                    super(OverrideTranslateSerializer, self).__init__(*args, **kwargs)
                   
                    if kwargs.get('context', None):
                        request = kwargs['context'].get('request', None)
                        forced_lang = request.QUERY_PARAMS.get('lang',None)
                        if forced_lang:
                            translation.activate(forced_lang)

          • curl --user admin:admin "http://127.0.0.1:8000/api/toto/1/?lang=fr" --header 'Accept-Language: ca'
            • will get fr version
          • curl -X PATCH -H "Content-Type:application/json" -d '{"name":"nouveau nom"}' --user admin:admin "http://127.0.0.1:8000/api/toto/1/?lang=fr" --header 'Accept-Language: ca'
            • will modify fr version
      • pip install django-modeltranslation
      • my_project
        • my_project
          • settings.py
            • INSTALLED_APPS = (
                  # must be in the first place
                  'modeltranslation',
                  'django.contrib.admin',
              ...
              )
            • # modeltranslation
              gettext = lambda s: s
              LANGUAGES = (
                  ('en', gettext('English')),
                  ('ca', gettext('Catalan')),
                  ('es', gettext('Spanish')),
                  ('fr', gettext('French')),
              )
            • # modeltranslation
              from django.utils.translation import ugettext_lazy as _
              LANGUAGES = (
                  ('en', _('English')),
                  ('ca', _('Catalan')),
                  ('es', _('Spanish')),
                  ('fr', _('French')),
              )
        • my_app
          • translation.py
            • from modeltranslation.translator import translator, TranslationOptions
              from my_app.models import MyModel

              class MyModelTranslationOptions(TranslationOptions):
                  fields = ('name', 'description',)

              translator.register(MyModel, MyModelTranslationOptions)
          • admin.py
            • from django.contrib import admin
              from modeltranslation.admin import TranslationAdmin, TabbedTranslationAdmin
              from my_app.models import MyModel

              class MyModelAdmin(TabbedTranslationAdmin):
              #class MyModelAdmin(TranslationAdmin):
                  group_fieldsets = False

              admin.site.register(MyModel, MyModelAdmin)
      • my_project amb herència / with inheritance
        • my_project
          • settings.py
            • INSTALLED_APPS = (
                  # must be in the first place
                  'modeltranslation',
                  'django.contrib.admin',
              ...
              )
            • # modeltranslation
              from django.utils.translation import ugettext_lazy as _
              LANGUAGES = (
                  ('en', _('English')),
                  ('ca', _('Catalan')),
                  ('es', _('Spanish')),
                  ('fr', _('French')),
              )
        • my_app
          • models.py
            • from django.db import models
              from model_utils.managers import InheritanceManager

              class Place(models.Model):
                  name = models.CharField(max_length=50)
                  address = models.CharField(max_length=80)

                  objects = InheritanceManager()

              class Restaurant(Place):
                  serves_hot_dogs = models.BooleanField(default=False)
                  serves_pizza = models.BooleanField(default=False)
                    
              class Bar(Place):
                  serves_alcohol = models.BooleanField(default=False)
                
          • translation.py
            • from modeltranslation.translator import translator, TranslationOptions
              from my_app.models import Place, Restaurant, Bar

              class PlaceTranslationOptions(TranslationOptions):
                  fields = ('name',)
              translator.register(Place, PlaceTranslationOptions)

              class RestaurantTranslationOptions(TranslationOptions):
                  pass
              translator.register(Restaurant, RestaurantTranslationOptions)

              class BarTranslationOptions(TranslationOptions):
                  pass
              translator.register(Bar, BarTranslationOptions)

          • admin.py
            • from django.contrib import admin
              from modeltranslation.admin import TranslationAdmin, TabbedTranslationAdmin

              from models import Place, Restaurant, Bar

              @admin.register(Place)
              class PlaceAdmin(TabbedTranslationAdmin):
                  list_display = ('name', 'type', 'address', )

              @admin.register(Restaurant)
              class RestaurantAdmin(TabbedTranslationAdmin):
                  pass

              @admin.register(Bar)
              class BarAdmin(TabbedTranslationAdmin):
                  pass

      • Problemes / Problems
    • Internationalization and localization of django models, with admin support (django-easymodel)

Tests

  • Testing in Django
    • Tutorial part 5
    • Integration with coverage.py
    • PostgreSQL
      • cannot create extension without superuser role
      • CentOS
        • su postgres
          psql
          # 1. temporarily set user as superuser:
          alter role user_name superuser;
          # 2. run tests, that will create postgis extension
          # 3. set user as no superuser:
          alter role user_name nosuperuser;

    • Exemples / Examples
      • disable logging
        • import logging

          class MyModelAPITestCase(APITestCase):
              def setUp(self):
                  logging.disable(logging.DEBUG)

      • rest_auth
        • tests.py
          • from django.test import TestCase

            from django.urls import reverse
            from django.contrib.auth.models import User

            from rest_framework import status
            from rest_framework.test import APITestCase

            from allauth.account.models import EmailAddress

            from my_app.models import MyModel
                  
            class MyModelAPITestCase(APITestCase):
                # run with: ./manage.py test my_app.tests.MyModelAPITestCase
                email = 'user@example.org'
                password = '...'
                key = ''

                def login(self):
                    url = reverse('rest_login')
                    data = {'email':self.email, 'password':self.password}
                    response = self.client.post(url, data, format='json')
                    self.key = response.data['key']
                    print u"key: {}".format(self.key)
               
                def register(self):
                    url = reverse('rest_register')
                    data = {'email':self.email, 'password1':self.password, 'password2':self.password}
                    response = self.client.post(url, data, format='json')
                    print u"response: {}".format(response)

                    # mark email as verified
                    email = EmailAddress.objects.get()
                    email.verified = True
                    email.save()

                def setUp(self):
                    # create a user
                    #user = User.objects.create_user(self.email,self.email,self.password)
                    # register a user
                    self.register()
               
                def test_create_mymodel(self):
                    """Create a mymodel (API REST)"""
                   
                    # login as user
                    self.login()
                    self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.key)
                   
                    url = reverse('mymodel-list')
                    data = {'name': 'test'}
                    response = self.client.post(url, data, format='json')
                    # optional headers:
                    #
            response = self.client.post(url, data, format='json', **{'HTTP_ACCEPT_LANGUAGE':'ca'})
                    self.assertEqual(response.status_code, status.HTTP_201_CREATED)
                    self.assertEqual(MyModel.objects.count(), 1)
                    self.assertEqual(MyModel.objects.get().name, 'test') 
                  

      • ./manage test [--keepdb] ...
  • Testing (Djangorestframework)

REST

  • REST client
  • Simple REST server
    • views.py
      • from django.http import HttpResponse
        from django.http import Http404
        from django.views.decorators.csrf import csrf_exempt
        import commands

        @csrf_exempt
        def toto(request):
            if request.method != 'POST':
                raise Http404

            cadena = request.POST['cadena']
            comanda = 'ls -l ' + cadena

            status, output = commands.getstatusoutput(comanda)

            return HttpResponse(output)
  • Django REST framework
    • tomchristie / django-rest-framework (Github)
    • Django REST Framework-Specific Standards
    • Documentation
    • Instal·lació / Installation
      • virtualenv + pip (e.g. for deployment with Apache)
        • # virtualenv -p python26 /opt/PYTHON26
          # chown apache.apache -R /opt/PYTHON26
          # source /opt/PYTHON26/bin/activate
          # pip install Django==1.4.3 djangorestframework==2.2.5 python-dateutil rfc3339
          # deactivate

      • virtualenv + pip (from git)
        • pip install -e git+git://github.com/tomchristie/django-rest-framework.git#egg=django-rest-framework
      • easy_install
        • # easy_install -U distribute
          # easy_install python-dateutil
          # easy_install rfc3339
          # easy_install [--upgrade] djangorestframework
    • Configuració / Setting
      • settings.py
        • INSTALLED_APPS = (
          ...
          'rest_framework',
          )
        • REST_FRAMEWORK = {
              'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
              'PAGINATE_BY': 10,
              ...

          }
      • data / date
        • settings.py
          • REST_FRAMEWORK = {
                ...
                # https://docs.python.org/2/library/datetime.html#strftime-strptime-behavior
                'DATETIME_INPUT_FORMATS': ['iso-8601','%Y-%m-%d %H:%M:%S'],
                #'
            DATETIME_FORMAT': 'iso-8601',
                # same as iso-8601, but force microseconds rendering when equal to 0
                'DATETIME_FORMAT': '%Y-%m-%dT%H:%M:%S.%fZ',
                'TIME_INPUT_FORMATS': ['iso-8601','PT%HH%MM%SS'],
                '
            TIME_FORMAT': 'PT%HH%MM%SS',
            }
        • si no s'especifiquen a settings.py, els formats de dates i temps depenen del renderer (per exemple, el renderer xml no fa iso-8601 per defecte)
          • si fa iso-8601, la precisió ha de ser de microsegons (%06N) i no de nanosegons
        • si s'especifica a settings, sempre es pot sobreescriure a serializers.py
          • start_date = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S')
          • start_time = serializers.TimeField(format='%H:%M:%S.%f')
        • deserialization (e.g. from tests)
          • from django.utils import timezone                                                                                                                                                                                                                                         
            now = timezone.now()
            begin = now + datetime.timedelta(minutes=5)
            url = reverse('mymodel-list')
            data = {'begin': begin}
            response = self.client.post(url, data, format='json')
        • filter
          • ...
    • Migration from old version to new one:
      • from rest_framework.renderers import BaseRenderer
        #from rest_framework.renderers import DEFAULT_RENDERERS
        from rest_framework.settings import DEFAULTS
        class UriListRenderer(BaseRenderer):
            media_type = 'text/uri-list'
            format = 'uri-list'
        ...
        class TotoView(View):
            ...
            renderers = DEFAULTS['DEFAULT_RENDERER_CLASSES'] + (UriListRenderer,)
            ...
    • Recursivitat / Recursivity
    • Biblioteques relacionades / Related libraries
    • Paginació / Pagination
    • Documenting your API
      • Django REST Swagger
        • Documentation (0.3)
        • Instal·lació / Installation
          • pip install django-rest-swagger
          • from git:
            • pip install -e git+git://github.com/marcgibbons/django-rest-swagger#egg=django-rest-swagger
        • Swagger
        • settings.py
          • INSTALLED_APPS = (
                ...
                'rest_framework_swagger',
                ...
            )
        • version 2.0
          • IMPORTANT: els mètodes que necessitin autenticació només apareixeran si feu «Authorize»
          • views.py
            • ...
          • urls.py
            • from rest_framework.schemas import get_schema_view
              from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer

              schema_view = get_schema_view(
                  title='my API',
                  renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer]
              )
              urlpatterns = [
                  # django-rest-swagger 0.3 documentation
                  #url(r'^docs/', include('rest_framework_swagger.urls')),
                  # django-rest-swagger 2.0 documentation
                  url(r'^docs/', schema_view),

        • version 0.3
          • my_project/urls.py
            • urlpatterns = [
                  # swagger 0.3 documentation
                  url(r'^docs/', include('rest_framework_swagger.urls')),
                  ...
              ]
        • Static documentation
        • Prioritat / Priority
          • YAML in views.py
          • help_text in serializers.py (if field is explicitely added in serializer, help_text is used, and if it is not specified, no final comment will be shown)
          • help_text in models.py
        • Problemes / Problems
          • Unable to read api 'v1' from path http://127.0.0.1:8000/docs/api-docs/v1 (server returned INTERNAL SERVER ERROR)
            • To debug: open the conflicitve path with a browser
          • HttpRequest has not query_params
            • Solution: when using get_serializer_class, take into account that swagger is accessing as HttpRequest instead of usual rest_framework.request.Request
              • views.py
                • from distutils.util import strtobool
                  from django.http import HttpRequest

                  class MyModelViewSet(viewsets.ModelViewSet):
                      """
                      My help
                      ---
                      retrieve:
                          parameters:
                              - name: extended
                                description: "Whether to return an extended description"
                                paramType: query
                                type: boolean
                      """
                      queryset = MyModel.objects.all()
                      serializer_class = MyModelSimpleSerializer
                      lookup_field = 'name'

                      def get_serializer_class(self):
                          # with extended flag:
                          # true: returns extended information
                          # false: returns basic information
                         
                          # for swagger
                          if isinstance(self.request, HttpRequest):
                              return MyModelExtendedSerializer
                          else:
                              show_extended = strtobool( self.request.query_params.get('extended','False') )
                              if show_extended:
                                  return MyModelExtendedSerializer
                              else:
                                  return MyModelSimpelSerializer
          • serializers.BaseSerializer not working
          • serializers.ListField( child=serializers.IntegerField()
          • Boolean
            • boolean type doesn't work properly #340
            • Solution
              • views.py
                • import django_filters

                  BOOLEAN_CHOICES = (('false', 'False'), ('true', 'True'),)
                  class MyModelFilterSet(django_filters.FilterSet):
                      # must be specified to allow "true" and false in lower case
                      is_free = django_filters.TypedChoiceFilter(choices=BOOLEAN_CHOICES,
                                                              coerce=strtobool,
                                                              help_text=_("Whether the object is free"))
          • Filter query parameters
        • YAML Docstring
          • 4.3.3 Data type fields
          • How to show query parameter options in Django REST Framework - Swagger
          • Syntax
            • yaml
              class MyModelFilterSet(django_filters.FilterSet):
              gui
              top level
              first level
              key
              value
              description
              field_1 = django_filters.XXFilter(...)
              Response class
              Parameters
              Error Status Codes
              with ViewSet with generics.xxxAPIView









              create:
              list:
              retrieve:
              update:
              partial_update:
              destroy:

              GET: (bug)
              POST:
              PATCH:
              PUT:
















              serializer:


              my_app.serializers.MyModelSerializer

              x


              request_serializer:


              ...


              ?


              response_serializer:


              ...


              x


              type:
              • field1:
              • field2:
              required:
              true
              when not using serializer, fields can be directly described here

              x


              false




              type:
              string




              integer




              boolean




              url




              ...




              parameters:
              (Parameter Object)
              (Data type fields)

              - name:
              my_name



              Parameter

              description:
              • my_description
              • "my desription with: colons"

              label=

              Description

              paramType:



              query
              get parameters: ?foo=...&bar=...

              Parameter Type





              form post parameters



              body all parameters (in json?)



              file file to upload



              ...




              pytype:
              • my_app.serializers.MoneySerializer
              • ..serializers.MoneySerializer



              x

              required: true



              (bold face)

              false



              (roman face)

              type:
              string



              Data Type






              integer




              boolean




              url




              ...




              type: array
              collectionFormat: csv
              items:
                type: string




              type: choice
              enum:
                - value_1
                - value_2
                - ...





              parameters_strategy

              • form:
              • query:
              • merge
              • replace





              omit_parameters:

              - query






              - path






              ...






              responseMessages:

              - code:
              403
              ...





              HTTP Status Code
              message: My message




              Reason







              widget

              Value
              Data Type


          • Query parameters for GET:
            • http://.../?foo=...&bar=...
            • class MyModelViewSet(viewsets.ModelViewSet):
                  """
                  Your docs.
                  ---
                  # YAML (must be separated by '---')
                  # if using colons in descriptions, put all the description inside quotes
                  retrieve:
                      parameters:
                          - name: detail
                            description: boolean to indicate whether to show specific details
                            paramType: query
                  """

            •     @detail_route()
                  def foo_view(self, request, **kwargs):
                      """
                      Your docs.
                      ---
                      # YAML (must be separated by '---')
                      # if using colons in descriptions, put all the description inside quotes

                      parameters:
                          - name: foo
                            description: description of foo parameter
                            paramType: query
                          - name: bar
                            description: "description of bar (e.g.: toto)"
                            paramType: query
                      responseMessages:
                          - code: 400
                            message: Your message about this code
                      """
          • Filter query parameters: show them only at GET
            • views.py
              • class MyModelViewSet(viewsets.ModelViewSet):
                    """
                    My help
                    ---
                    create:
                        omit_parameters:
                            - query

                    retrieve:
                        omit_parameters:
                            - query

                    update:
                        omit_parameters:
                            - query

                    partial_update:
                        omit_parameters:
                            - query

                    destroy:
                        omit_parameters:
                            - query
          • Headers (*)
            • parameters:
                  - name: Authorization
                    paramType: header
                    required: true
          • Override serializer by a simple description
            • views.py
              • ...
          • Choices
            • Using the string label instead of integer value
            • views.py
              • class ...
                        """
                        ---
                        parameters:
                            - name: my_choice_field_1
                              description: "My description"
                              paramType: form
                              type: choice
                              enum:
                                - my_value_option_1
                                - my_value_option_2
                        """

          • List, array
            • Swagger UI doesn't seem to support Arrays yet? #982
            • With yuml (not working)
              • views.py
                • class MyModelViewSet(viewsets.ModelViewSet):
                      """
                      My help
                      ---
                      create:
                          parameters:
                              - name: prices
                                description: "Array of prices"
                                type: array
                                items:
                                  type: string
                                collectionFormat: csv

          • Simple serializer in a detail_route
            • Option 1 (recommended one) Serializer specification is also needed in yaml description
              • views.py
                • from my_app.serializers import MyDetailSerializer

                  class MyModelViewSet(viewsets.ModelViewSet):
                      serializer_class=MyModelSerializer
                      ...
                     
                      @detail_route(serializer_class=MyDetailSerializer)
                      def status(self, request, **args, **kwargs):
                          """
                          My help.
                          ---
                          serializer: MyDetailSerializer
                          """
                          input_data = dict()
                          input_data['field1'] = 0
                          input_data['field2'] = 1

                          # serializer specified inside the decorator
                          serializer = self.get_serializer(data=input_data)
                          serializer.is_valid(raise_exception=True)
                             
                          return Response(serializer.data)

              • serializers.py
                • class MyDetailSerializer(serializers.Serializer):
                      field1 = serializers.IntegerField()
                      field2 = serializers.IntegerField()
            • Option 2
              • views.py
                • # even if not used MyDetailSerializer must be included in order to drf-swagger to work
                  from my_app.serializers import MyDetailSerializer

                  class MyModelViewSet(viewsets.ModelViewSet):
                      ...
                     
                      @detail_route()
                      def status(self, request, **args, **kwargs):
                          """
                          My help.
                          ---
                          serializer: MyDetailSerializer
                          """
                          ...
              • serializers.py
                • class MyDetailSerializer(serializers.Serializer):
                      field1 = serializers.IntegerField()
                      field2 = serializers.IntegerField()


          • File upload
            • (?) Not working!  Even if paramType is specified as form, in the GUI it shows as body, and any request gives a "403 undefined" error:
              • "detail": "CSRF Failed: CSRF token missing or incorrect."
            • views.py
              • class MyModelViewSet(viewsets.ModelViewSet):
                    """
                    My help.
                    ---
                    create:
                        parameters:
                            - name: thumbnail
                              description: Thumbnail image to upload
                              type: file
                              paramType: form

                              consumes: multipart/form-data
          • Nested writable serializers
    • Autenticació / Authentication
      • Tutorial 4: Authentication and permissions
      • API Guide: Authentication

        • settings.py
          credentials when successfully authenticated
          client

          REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES
          INSTALLED_APPS
          request-user
          request.auth
          curl requests
          built-in
          rest_framework.authentication.BasicAuthentication

          User
          None
          curl -X GET -u admin:abc123 http://127.0.0.1:8000/api/example/
          import requests
          from requests.auth import HTTPBasicAuth
          r = requests.get('http://127.0.0.1:8000/v1/api/totos/', auth=HTTPBasicAuth('admin', 'abc123'))
          rest_framework.authentication.TokenAuthentication rest_framework.authtoken
          User rest_framework.authtoken.models.BasicToken
          curl -X GET http://127.0.0.1:8000/api/example/ -H 'Authorization: Token <admin_token>'

          rest_framework.authentication.SessionAuthentication
          User None

          3rd party
          DigestAuthentication



          curl -X GET --digest -u admin:abc123 http://127.0.0.1:8000/api/example/ import requests
          from requests.auth import HTTPDigestAuth
          r = requests.get('http://127.0.0.1:8000/v1/api/totos/', auth=HTTPDigestAuth('admin', 'abc123'))
          JWT
          rest_framework_jwt.authentication.JSONWebTokenAuthentication



          curl -X POST -d "username=admin&password=abc123" http://localhost:8000/api-token-auth/
          curl -X GET http://127.0.0.1:8000/api/example/ -H 'Authorization: JWT <your_token>'
          import requests
          import json
          payload = {'username': 'admin', 'password': 'abc123'}
          r1 = requests.post('http://127.0.0.1:8000/api-token-auth/', headers={'Content-Type':'application/json'}, data=json.dumps(payload) )
          token = r1.json()['token']
          r2 = requests.get('http://127.0.0.1:8000/v1/api/totos/', headers={'Authorization':'JWT %s'%token})
        • JWT
          • pip install djangorestframework-jwt
          • my_project/my_project/settings.py
            • REST_FRAMEWORK = {
                  'DEFAULT_PERMISSION_CLASSES': (
                      'rest_framework.permissions.IsAuthenticated',
                  ),
                  'DEFAULT_AUTHENTICATION_CLASSES': (
                      'rest_framework.authentication.SessionAuthentication',
                      'rest_framework.authentication.BasicAuthentication',
                      'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
                  ),
              }
            • JWT_AUTH = {
                  ...
              }

          • my_project/my_project/urls.py
            • urlpatterns = patterns('',
                  # ...

                  url(r'^api-token-auth/', 'rest_framework_jwt.views.obtain_jwt_token'),
              )
          • Token expiration
            • my_project/my_project/settings.py
              • JWT_AUTH = {
                    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3600),
                }
          • Token refresh and verify
            • my_project/my_project/settings.py
              • JWT_AUTH = {
                    'JWT_ALLOW_REFRESH': True,
                }
            • my_project/my_project/urls.py
              • urlpatterns = patterns('',
                    url(r'^api-token-verify/', 'rest_framework_jwt.views.verify_jwt_token'),
                    url(r'^api-token-refresh/', 'rest_framework_jwt.views.refresh_jwt_token'),
                )

          • Where can I override jwt_response_payload_handler method?
            • my_project/settings.py
              • JWT_AUTH = {
                        'JWT_RESPONSE_PAYLOAD_HANDLER': 'my_app.utils.jwt_response_payload_handler',
                }
            • my_app/utils.py
              • from my_app.serializers import UserSerializer

                def jwt_response_payload_handler(token, user=None, request=None):
                    return {
                        'token': token,
                        'user': UserSerializer(user).data
                    }

          • test
            • using slumber (NOTE: does not work with '-' in urls)
              • import slumber
                a = slumber.API("http://127.0.0.1:8000/")
                a.apitokenauth.post({"username":"admin", "password":"admin"})

    • Filtratge / Filtering

      • URL
        settings.py
        urls.py
        views.py
        filters.py
        Against the current user
        http://example.com/api/purchases/

        class MyModelViewSet(viewsets.ModelViewSet)

            def get_queryset(self)
                user = self.request.user
                return Purchase.objects.filter(purchaser=user)

        Against the URL
        http://example.com/api/purchases/denvercoder9
        url('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),
        class MyModelViewSet(viewsets.ModelViewSet)

            def get_queryset(self)
                username = self.kwargs['username']
                return Purchase.objects.filter(purchaser__username=username)

        Against query parameters
        http://example.com/api/purchases?username=denvercoder9

        class MyModelViewSet(viewsets.ModelViewSet)

            def get_queryset(self)
                queryset = Purchase.objects.all()
                username = self.request.QUERY_PARAMS.get('username', None)
                if username is not None:
                    queryset = queryset.filter(purchaser__username=username)
                return queryset

        Generic filtering (simple field)
        http://example.com/api/purchases?type=denvercoder9
        REST_FRAMEWORK = {
            'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
        }


        # from rest_framework.filters import DjangoFilterBackend

        class PlaceViewSet(viewsets.ModelViewSet):
            # if not specified in DEFAULT_FILTER_BACKENDS:
            # filter_backends = (DjangoFilterBackend,)
           
            # DjangoFilterBackend
            filter_fields = ('type',)

        Generic filtering (FilterSet)

        # from rest_framework.filters import DjangoFilterBackend

        import django_filters
        class MyFilterSet(django_filters.FilterSet):
            ...

        class PlaceViewSet(viewsets.ModelViewSet):
            # if not specified in DEFAULT_FILTER_BACKENDS:
            # filter_backends = (DjangoFilterBackend,)
           
            # DjangoFilterBackend
            filter_class = MyFilterSet
        class IsoDateTimeRangeField(django_filters.fields.RangeField):
        ...
        class BooleanMethodFilter(django_filters.MethodFilter):
        ...

        Search
        http://...?search=... REST_FRAMEWORK = {
            'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.SearchFilter',),
        }

        # from rest_framework.filters import SearchFilter

        class PlaceViewSet(viewsets.ModelViewSet):
            # if not specified in DEFAULT_FILTER_BACKENDS:
            # filter_backends = (SearchFilter,)

            # SearchFilter: search at any place (@) in field name:
            search_fields = ('@name',)

        Ordering
        http://...?ordering=-field_a


        from rest_framework.filters import OrderingFilter

        class MyModelViewSet(viewsets.ModelViewSet)
            filter_backends = (OrderingFilter,)

            # OrderingFilter: fields may be ordered against
            ordering_fields = ('field_a','field_b',)

            # OrderingFilter: default ordering
            ordering = ('-field_c',)

        Custom



        from rest_framework.filters import BaseFilterBackend

        class IsOwnerFilterBackend(filters.BaseFilterBackend):
            
        """
             Filter that only allows users to see their own objects.
             """

            
        def filter_queryset(self, request, queryset, view):
                
        return queryset.filter(owner=request.user)

        class MyModelViewSet(viewsets.ModelViewSet)
            filter_backends = (IsOwnerFilterBackend,)


      • Generic filtering
        • django-filter (used by DjangoFilterBackend)
          • pip install django-filter
          • Migrating to 1.0
          • my_project
            • my_project
              • settings.py
                • REST_FRAMEWORK = {
                      'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
                  }

            • my_app
              • views.py
                • class PlaceViewSet(viewsets.ModelViewSet):

                      filter_fields = ('type',)
          • ForeignKey Slug
            • models.py
            • Option 1:
              • views.py
                • class BookViewSet(viewsets.ModelViewSet):

                      filter_fields = ('publisher__name',)
              • url
                • http://localhost:8000/v1/api/books/?publisher__name=...
            • Option 2 (without double underscore):
              • views.py (>=1.0)
                • class BookFilterSet(django_filters.FilterSet):
                      publisher = django_filters.CharFilter(method='filter_publisher')
                     
                      def filter_publisher(self, queryset, name, value):
                          qs = queryset.filter(publisher__name__iexact=value)     
                          return qs
                    
                      class Meta:
                          model = NotificationSetting
                          fields = ['publisher',]

                  class BookViewSet(viewsets.ModelViewSet):

                      filter_class = BookFilterSet
              • views.py (<1.0)
                • class BookFilterSet(django_filters.FilterSet):
                      publisher = django_filters.MethodFilter(action='get_publisher')
                     
                      def get_publisher(self, queryset, value):
                          qs = queryset.filter(publisher__name__iexact=value)     
                          return qs
                    
                      class Meta:
                          model = NotificationSetting
                          fields = ['publisher',]

                  class BookViewSet(viewsets.ModelViewSet):

                      filter_class = BookFilterSet
              • url
                • http://localhost:8000/v1/api/books/?publisher=...
          • Multiple values
          • IsoDateTime
            • Filter on range of datetimes in django rest framework
            • my_app/models.py
              • class MyModel(models.Model):
                    begin = models.DateTimeField()

            • my_app/filters.py
              • import django_filters

                # we need to redefine it because DateTimeRangeField does not work correctly with ISO datetime (YYYY-MM-DDThh:mm:ss.mmmmmmZ)

                class IsoDateTimeRangeField(django_filters.fields.RangeField):
                    def __init__(self, *args, **kwargs):
                        fields = (
                            django_filters.fields.IsoDateTimeField(),
                            django_filters.fields.IsoDateTimeField())
                        super(IsoDateTimeRangeField, self).__init__(fields, *args, **kwargs)

                    def compress(self, data_list):
                        if data_list:
                            start_datetime, stop_datetime = data_list
                            return slice(start_datetime, stop_datetime)
                        return None

                class IsoDateTimeFromToRangeFilter(django_filters.RangeFilter):
                    field_class = IsoDateTimeRangeField

            • my_app/views.py
              • import django_filters
                from my_app.models import MyModel
                from my_app.filters import IsoDateTimeFromToRangeFilter

                class MyModelFilter(django_filters.FilterSet):
                   
                    begin = IsoDateTimeFromToRangeFilter()

                    class Meta:
                        model = MyModel
                        fields = ['begin',]

                class MyModelViewSet(viewsets.ModelViewSet):
                    ...
                    filter_class = MyModelFilter

            • Ús / Usage
              • http://localhost:8000/v1/api/mymodels/?begin_0=2015-10-31T16:00:00Z&begin_1=2015-10-31T16:31:00Z
          • MethodFilter
            • IMPORTANT: MethodFilter and Filter.action replaced by Filter.method
            • How use MethodFilter from django-filter app?
            • my_app/models.py
              • class MyModel(models.Model):
                    begin = models.DateTimeField(_("Begin"), help_text=_("Begin date"))
                    end = models.DateTimeField(_("End"), help_text=_("End date"))
            • my_app/views.py
              • import datetime
                import django_filters

                class MyModelFilter(django_filters.FilterSet):
                    next = django_filters.MethodFilter(action='get_next')

                    def get_next(self, queryset, value):
                        """
                        Filter the objects that begin between now and (now+delta).
                        """
                        qs = queryset
                        if value != '':
                            # get hours, minutes and seconds from query (hh:mm:ss)
                            h,m,s = ( int(d) for d in value.split(':') )
                            delta = datetime.timedelta( hours=h, minutes=m, seconds=s )
                            # get now in UTC
                            now = timezone.now()
                           
                            qs = queryset.filter( begin__gte=now, begin__lte=now+delta)
                        return qs
                   
                    class Meta:
                        model = MyModel
                        fields = ['next',]
            • Ús / Usage
              • list of objects that begin within the next 10 minutes and 20 seconds:
                • http://127.0.0.1:8000/v1/api/mymodels/?next=00:10:20
          • Boolean (lower case)
            • my_app/models.py
              • class MyModel(models.Model):
                    is_ok = models.BooleanField(default=True)
            • my_app/views.py
              • coerce: cast to a type
              • from distutils.util import strtobool
                i
                mport django_filters
                from my_app.models import MyModel

                BOOLEAN_CHOICES = (('false', 'False'), ('true', 'True'),)
                class MyModelFilter(django_filters.FilterSet):
                    # must be specified to allow "true" and false in lower case
                    is_ok = django_filters.TypedChoiceFilter(choices=BOOLEAN_CHOICES, coerce=strtobool)

                    class Meta:
                        model = MyModel
                        fields = ['is_ok',]
            • Ús / Usage
              • http://localhost:8000/v1/api/mymodels/?is_active=true
          • MethodFilter with boolean
            • IMPORTANT: MethodFilter and Filter.action replaced by Filter.method
            • my_app/models.py
              • class MyModel(models.Model):
                    begin = models.DateTimeField(_("Begin"), help_text=_("Begin date"))
                    end = models.DateTimeField(_("End"), help_text=_("End date"))
            • my_app/filters.py
              • import django_filters

                class BooleanMethodFilter(django_filters.MethodFilter):
                    field_class = forms.NullBooleanField

            • my_app/views.py
              • from my_app.filters import BooleanMethodFilter

                BOOLEAN_CHOICES = (('false', 'False'), ('true', 'True'),)
                class MyModelFilterSet(django_filters.FilterSet):
                    is_active = BooleanMethodFilter(action='get_active', choices=BOOLEAN_CHOICES, help_text=_("Filter by objects that are active"))
                   
                    def get_active(self, queryset, value): 
                        # get now in UTC
                        now = timezone.now()
                       
                        qs = queryset
                        if value != '':
                            value_bool = strtobool(value)
                            if value_bool:
                                qs = queryset.filter( begin__lte=now, end__gte=now )
                            else:
                                qs = queryset.filter( Q(begin__gte=now) | Q(end__lte=now) )
                           
                        return qs


                    class Meta:
                        model = MyModel
                        fields = ['is_active',]

          • Choices
            • models.py
              • class TotoModel(models.Model):
                   
                FIELD_A_CHOICES = (
                                     (0,'first'),
                                     ...
                                     )
                    field_a = models.IntegerField(choices=
                FIELD_A_CHOICES)
            • views.py
              • import django_filters
                from my_app.models import TotoModel

                class TotoFilter(django_filters.FilterSet):
                    field_a = django_filters.ChoiceFilter(choices=TotoModel.FIELD_A_CHOICES, help_text="Filter by field_a: %s" % ', '.join([v for k, v in
                TotoModel.FIELD_A_CHOICES]))
                   
                    def __init__(self, data=None, queryset=None, prefix=None, strict=None, request=None):
                        if 'field_a' in data:
                            field_a = data['field_a']
                            # if it is not already a digit, invert key/values
                            if not field_a.isdigit():
                                try:
                                    inverted_choices = dict((v, k) for k, v in TotoModel.FIELD_A_CHOICES)
                                    # conversion to make it writable
                                    data = dict(data)
                                    data['field_a'] = inverted_choices[field_a]
                                except:
                                    pass
                 
                        super(TotoFilter, self).__init__(data, queryset, prefix, strict)
                   
                    class Meta:
                        model = TotoModel
                        fields = ['field_a',]

                class TotoViewSet(viewsets.ModelViewSet):
                    queryset = TotoModel.objects.all()
                    serializer_class = TotoSerializer
                    filter_class = TotoFilter
            • Usage
              • http://.../totos?field_a=first
              • http://.../totos?field_a=0
        • django-rest-framework-filters
    • Usuaris / Users
      • Recupera i utilitza l'usuari de la petició / Get and use the user from request
      • Serialització d'usuaris i grups / Users and groups serialization
      • Extended user (with OneToOneField)
        • Extended user with django-rest-auth (USER_DETAILS_SERIALIZER)
        • Django Rest Framework make OnetoOne relation ship feel like it is one model
          • models.py
            • class Profile(models.Model):
                  user = models.OneToOneField(User)
                  avatar_url = models.URLField(default='', blank=True)  # e.g.
          • serializers.py
            • class UserSerializer(serializers.HyperlinkedModelSerializer):
                  # A field from the user's profile:
                  avatar_url = serializers.URLField(source='profile.avatar_url', allow_blank=True)

                  class Meta:
                      model = User
                      fields = ('url', 'username', 'avatar_url')

                  def create(self, validated_data):
                      profile_data = validated_data.pop('profile', None)
                      user = super(UserSerializer, self).create(validated_data)
                      self.create_or_update_profile(user, profile_data)
                      return user

                  def update(self, instance, validated_data):
                      profile_data = validated_data.pop('profile', None)
                      self.create_or_update_profile(instance, profile_data)
                      return super(UserSerializer, self).update(instance, validated_data)

                  def create_or_update_profile(self, user, profile_data):
                      profile, created = Profile.objects.get_or_create(user=user, defaults=profile_data)
                      if not created and profile_data is not None:
                          super(UserSerializer, self).update(profile, profile_data)

    • Requests and responses
      • request (curl -H "Content-Type:...")
        • application/json
        • application/x-www-form-urlencoded
        • multipart/form-data
      • response (curl -H "Accept: ...")
        • application/json
        • text/html
      • Exemples / Examples:
        • curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/



      • curl
        python-requests
        request





        parser





        JSONParser
        -X POST
        -X PUT
        -X PATCH





        -H "Content-Type:application/json"
        -d '{"key":"value"}'
        YAMLParser
        -H "Content-Type:application/yaml"
        -d ...
        XMLParser
        -H "Content-Type:application/xml" -d ...
        FormParser
        [-H "Content-Type:application/x-www-form-urlencoded"] -d 'key=value'

        MultiPartParser
        -H "Content-Type:multipart/form-data" -d ...
        FileUploadParser
        -H "Content-Type:*/*" -d ...
        response
        renderer
        JSONRenderer
        -X GET
        -H 'Accept: application/json; indent=4'


    • Problem:
      • >>> from rest_framework import renderers
        • ImportError: User model is not to be found.
        • (ImportError: Settings cannot be imported, because environment variable DJANGO_SETTINGS_MODULE is undefined.)
        • Solution:
          • ...
    • Media upload
      • settings.py
        • # media files
          MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
          MEDIA_URL = '/mm/'

      • my_app/models.py
        • class Cover(models.Model):
              name = models.CharField( max_length=100 )
              media = models.ImageField( upload_to='im', blank=True, null=True)

              def __unicode__(self):
                  return u'%s' % (self.name)

      • my_app/serializers.py
        • class CoverSerializer(serializers.ModelSerializer):   
              class Meta:
                  model = Cover
                  fields = ('name','media',)
      • my_app/views.py
        • from rest_framework.parsers import JSONParser, MultiPartParser

          class CoverViewSet(viewsets.ModelViewSet):
              queryset = Cover.objects.all()
              serializer_class = CoverSerializer
              parser_classes = (JSONParser, MultiPartParser,)
      • client
        • create a cover
          • python-requests
            • import requests
              from requests.auth import HTTPDigestAuth

              username = 'admin'
              password = 'admin'
              payload = {'name': 'cover 6'}
              r = requests.post(server_url+'/v1/api/covers/', auth=HTTPDigestAuth(username, password), data=payload, files={'media': open('test.png', 'rb')})

    • Excepcions / Exceptions
      • views.py
        • class MyModelViewSet(viewsets.ModelViewSet):

              def get_queryset(self):
                  # only entries that belong to specified parent
                  uuid = self.kwargs.get('uuid', None)
                  try:
                      parent_object = get_object_or_404(MyParentModel, uuid=uuid)
                      list_objects = MyModel.objects.filter(parent=parent_object)
                      return list_objects
                  except Exception as e:
                      raise e

        • from django.http import Http404

          class MyModelViewSet(viewsets.ModelViewSet):

              def get_queryset(self):
                  # only entries that belong to specified parent
                  uuid = self.kwargs.get('uuid', None)
                  try:
                      parent_object = get_object_or_404(MyParentModel, uuid=uuid)
                      list_objects = MyModel.objects.filter(parent=parent_object)
                      return list_objects
                  except Exception as e:
                      raise Http404

        • from rest_framework.exceptions import APIException, ValidationError, ParseError
          from rest_framework import status
          from rest_framework.decorators import detail_route

          class MyModelViewSet(viewsets.ModelViewSet):
              ...
              def perform_create(self, serializer):
                  try:
                      ...
                  except Exception as e:
                      ...
                      # returned status code: 500
                      raise APIException("Error when creating my_model %s: %s" % (serializer.validated_data['name'], e))

              def perform_update(self, serializer):
                  try:
                      ...
                  except Exception as e:
                      ...
                      # returned status code: 400
                      raise ValidationError("Error when updating my_model %s: %s" % (serializer.validated_data['name'], e))
               ...
                  try:
                      ...
                  except ValueError:
                      raise ParseError('Invalid ...')

              @detail_route()
              def my_function(self, request, **args, **kwargs):
                  try:
                      message = ...
                  except Exception as e:
                      return Response({'detail': '%s'%e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
                  return Response({'response': message})

        • from rest_framework.exceptions import APIException

          class MyAPIException(
          APIException):
              status_code = 400
              default_detail = 'Something is not correct.'

          class MyModelViewSet(viewsets.ModelViewSet):
              ...
              def perform_create(self, serializer):
                  try:
                      ...
                  except Exception as e:
                      ...
                      raise MyAPIException()


        • from rest_framework import status
          from rest_framework import generics

          class MyOtherModelCreateView(generics.CreateAPIView)
              ...
              # based on CreateModelMixin
              def create(self, request, *args, **kwargs):
                  serializer = self.get_serializer(data=request.data)
                  serializer.is_valid(raise_exception=True)
                  self.perform_create(serializer)
                  try:
                      ...
                  except Exception as e:
                      return Response({'detail':'%s'%e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
                  headers = self.get_success_headers(serializer.data)
                  return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
           
      • serializers.py
        • class MyModelSerializer(serializers.ModelSerializer):
              def create(self, validated_data):
                  try:
                      application, created = self.Meta.model.objects.get_or_create(**validated_data)
                  except Exception as e:
                      logger.error("[MyModelSerializer.create] Error: %s" % e)
                      raise e
                  return application
        • from rest_framework import serializers
          from django.db.utils import IntegrityError

          class MyModelSerializer(serializers.ModelSerializer):
              def create(self, validated_data):
                  try:
                      application, created = self.Meta.model.objects.get_or_create(**validated_data)
                  except IntegrityError:
                      logger.error("[MyModelSerializer.update] IntegrityError")
                      raise serializers.ValidationError({"my_field":["The value of my_field must be unique."]})           
                  except Exception as e:
                      logger.error("[MyModelSerializer.create] Error: %s" % e)
                      raise
          ValidationError("Error when creating.")
                  return application
        • and exception has to be captured at perform_create in views.py ViewSet
    • Resum general / General summary: parser, deserialization, validation, model, queryset, serialization, render


      parser
      deserialization (write)
      validation
      model
      queryset
      serialization (read)
      render
      definition
      parsers.py
      ...






      serializers.py
      class PlaceSerializer(serializers.ModelSerializer):
          def to_internal_value(self, data):
              ...

      class HexIntegerField(serializers.WritableField):
          def to_internal_value(self, data):
              # convert from hex in unicode ("0x02") to integer (2)
              return int(data, 16)
      class PlaceSerializer(serializers.ModelSerializer):
          def validate(self, attrs):
              # serializer.errors == {'non_field_errors': ['A non field error']}
              raise serializers.ValidationError('A non field error')

          def validate(self, attrs):
              # serializer.errors == {'my_field': ['A field error']}
              raise serializers.ValidationError({'my_field': 'A field error'})

          def validate_<field_name>(self, value):
              ...
      def create():
          ...

      def update():
          ...

      class PlaceSerializer(serializers.ModelSerializer):
          def to_representation(self,obj):
              ...

      class HexIntegerField(serializers.WritableField):
          def to_representation(self, obj):
              return "0x%02x" % (obj)

      models.py







      renders.py






      ...
      usage
      views.py
      class PlaceViewSet(viewsets.ModelViewSet):
          parser_classes = (JSONParser, MultiPartParser,)
      class PlaceViewSet(viewsets.ModelViewSet):
          serializer_class = PlaceSerializer


      class PlaceViewSet(viewsets.ModelViewSet):
          queryset = Place.objects.all()
          filter_fields = ('category', 'in_stock')
      class PlaceViewSet(viewsets.ModelViewSet):
          serializer_class = PlaceSerializer
      class PlaceViewSet(viewsets.ModelViewSet):
          renderer_classes = (BrowsableAPIRenderer, JSONRenderer, CSVRenderer)
          def create(self, request, *args, **kwargs):
              data = None
            
              # data is sent as direct json (literally or from file: @) (JSONParser):
              # curl -X POST -H "Content-Type:application/json" -u admin:admin http://127.0.0.1:8000/totos/ -d '{"a":"b"}'
              # curl -X POST -H "Content-Type:application/json" -u admin:admin http://127.0.0.1:8000/totos/ -d @toto.json
              if request._content_type == 'application/json':
                  data = request.DATA

              # data is sent inside a file that is uploaded (MultiPartParser):
              # curl -X POST -H "Content-Type:multipart/form-data" -u admin:admin http://127.0.0.1:8000/totos/ -F "file=@toto.json;type=application/json"
              # note: if type is not specified, it defaults to "application/octet-stream"
              else:
                  print request.FILES['file']
                  fitxer = File(request.FILES['file'])
                  content = fitxer.read()
                  stream = BytesIO(content)
                  if request.FILES['file'].content_type == 'application/json':
                      input_data = JSONParser().parse(stream)
                  else:
                      print "No parser for content type: %s" % (request.FILES['file'].content_type)
                      errors = dict()
                      errors['error'] =  "No parser for content type: %s" % (request.FILES['file'].content_type)
                      return Response(errors,status=status.HTTP_400_BAD_REQUEST)
                  fitxer.close()

                  # ... (follow to next column ->)
          def get_serializer_class(self):
              return PlaceSerializer

          def create(self, request, *args, **kwargs):  
              # ... (-> from previous column)

              # serialize the data:
              serializer = self.get_serializer(data=input_data, files=request.FILES, many=True)
            
              # if all is fine, save the object in the database:
              if serializer.is_valid():
                  self.pre_save(serializer.object)
                  self.object = serializer.save(force_insert=True)
                  self.post_save(self.object, created=True)
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


          def get_queryset(self):
              return MyModel.objects.filter(owner=self.request.user)

          filter_class = ProductFilter

      class ProductFilter(django_filters.FilterSet):
          ...


      actions.py
      def parse_mymodel_uploaded_file(uploaded_file, content-type):
          if content_type == 'application/json':
              fitxer = File(uploaded_file)
              content = fitxer.read()
              stream = BytesIO(content)
              input_data = JSONParser().parse(stream)
              fitxer.close()
             
              # ... (follow to next column ->)
      def parse_mymodel_uploaded_file(uploaded_file, content-type):
              # ... (-> from previous column)

              serializer = MyModelSerializer(data=input_data, many=True)
              if serializer.is_valid():
                  serializer.save(force_insert=True)







    • Routers (and ViewSets)
      • generated by router.register

        provided by ViewSet
        URL style
        URL name HTTP method
        action
        affected by:
        {prefix}/
        {basename}-list GET
        list
        • queryset
        • [def get_queryset]
          • def get_queryset(self):
                # only entries that belong to user
                user = self.request.user
                # (this model has user as a foreignkey)
                return user.thisobject_set.all()
          • def get_queryset(self):
                # only entries that belong to user
                u = self.request.user
                # (this model has user as a foreignkey)
                return Thismodel.objects.filter(user=u)
        POST
        create
        • [def pre_save]
          • def pre_save(self, obj):
                # toto_id is passed using the serializer, and is used to select
                # an otherobject, referenced as a ForeignKey
                try:
                    otherobject = Othermodel.objects.get(toto_id=self.request.DATA['toto_id'])
                except Othermodel.DoesNotExist:
                    raise Http404
                obj.otherobject = otherobject
        • [def create]
          • # drf 2.x
            def create(self, request, *args, **kwargs):
                serializer = self.get_serializer(data=request.DATA, files=request.FILES)
                if serializer.is_valid():
                    self.pre_save(serializer.object)
                    #set_password
                    serializer.object.set_password(serializer.data['password'])
                    self.object = serializer.save(force_insert=True)
                    self.post_save(self.object, created=True)
                    return Response(serializer.data, status=status.HTTP_201_CREATED)
                return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
          • # drf 3.x
            def perform_create(self, serializer):
                user = serializer.save()
                # set the password
                user.set_password(serializer.validated_data['password'])
                user.save()

        {prefix}/{lookup}/
        {basename}-detail GET
        retrieve
        • queryset
        • [def get_queryset]
        • [lookup_field]
        • [def get_object]
          • def get_object(self):
                # as extendeduser pk is not explicitly provided,
                # get it from the user on the request
                p = self.request.user
                try:
                    return ExtendedUser.objects.get(pk=p.id)
                except ExtendedUser.DoesNotExist:
                    raise Http404
        • [def pre_save]
          • def pre_save(self, obj):
                # as extendeduser pk is not explicitly provided,
                # get it from the user on the request
                obj.extendeduser = self.request.user
        PUT
        update
        PATCH
        partial_update
        DELETE
        destroy
        {prefix}/{lookup}/{methodname}/ {basename}-{method-name} GET
        @detail_route()
        @list_route()
        def method_name(...)
        POST
        @detail_route(methods=['post'])
        @list_route(methods=['post'])
        def method_name(...)
      • Resum de Routers i Viewsets / Summary:

        my_project/urls.py



        urlpatterns = patterns('',
            url(r'^v1/api/', include('my_app_1.urls', namespace='v1')),
            url(r'^v1/api/', include('my_app_2.urls', namespace='v1')),
        )




        my_app_1/url.py
        api_root (views.py)
        views.py
        ViewSet (and derived) with list,
        + Routers
        from rest_framework.routers import DefaultRouter

        router = DefaultRouter()
        router.register(r'totos', views.TotoViewSet, base_name='toto')
        urlpatterns = patterns('',
            url(r'^', include(router.urls)),
        )
        • from rest_framework import viewsets
          class TotoViewSet(viewsets.ReadOnlyModelViewSet):
          ...
        • class TotoViewSet(viewsets.ModelViewSet):
              """
              API endpoint that allows a toto to be viewed or edited.
              """
              queryset = MyModel.objects.all()
              serializer_class = MyModelSerializer

        from rest_framework.routers import SimpleRouter

        router = SimpleRouter()
        router.register(r'totos', views.TotoViewSet, base_name='toto')
        urlpatterns = patterns('',
            url(r'^', include(router.urls)),
        )
        from rest_framework.decorators import api_view
        from rest_framework.response import Response
        from rest_framework.reverse import reverse

        @api_view(('GET','HEAD'))
        def api_root(request, format=None):
            return Response({
                 'totos': reverse('toto-list', request=request, format=format),
            })
        ViewSet (and derived) without list,
        + Routers
        router = SimpleRouter()
        router.register(r'new_toto', views.TotoCreateViewSet, base_name='newtoto')
        urlpatterns = patterns('',
            url(r'^', include(router.urls)),
        )
        @api_view(('GET','HEAD'))
        def api_root(request, format=None):
            return Response({
                 'new_toto': reverse('newtoto-list', request=request, format=format),
            })
        • class CreateViewSet(mixins.CreateModelMixin,
                              viewsets.GenericViewSet):
              pass
          class TotoCreateViewSet(viewsets.CreateViewSet):
              queryset = Toto.objects.all()
              serializer_class = TotoSerializer
        APIView (and derived)
        from django.conf.urls import include

        urlpatterns = patterns('',
            url(r'^update_usuari/$', views.UsuariUpdateAPIView.as_view(), name='updateusuari-detail' ),
            url(r'^$', 'api_root'),
        )

        # Login and logout views for the browsable API
        urlpatterns += patterns('',
            url(r'^api-auth/', include('rest_framework.urls',
                                       namespace='rest_framework')),
        )

        @api_view(('GET','HEAD'))
        def api_root(request, format=None):
            return Response({
                 'update_usuari': reverse('updateusuari-detail', request=request, format=format),
            })
        • from rest_framework.renderers import JSONRenderer, XMLRenderer, BrowsableAPIRenderer

          class UsuariUpdateAPIView(generics.UpdateAPIView):
              ...
              renderer_classes = (BrowsableAPIRenderer, JSONRenderer,)
              ...
              # as participant pk is not explicitly provided,
              # get it from the user on the request   
              def get_object(self):
                  u = self.request.user
                  try:
                      return Participant.objects.get(pk=u.id)
                  except Participant.DoesNotExist:
                      raise Http404
      • router automatically creates, from a ViewSet:
        • urls
        • url names
      • api_root automatically generated by DefaultRouter (but not by SimpleRouter)
      • registre / registration (urls.py):
        • from django.conf.urls import patterns
          from rest_framework.routers import SimpleRouter

          router = SimpleRouter()
          router.register(r'prefix', viewset, base_name='basename')
          urlpatterns = patterns('',
              url(r'^', include(router.urls)),
          )
      • alternative to router (use *APIView instead of ViewSet):
        • urlpatterns = patterns('',
              url(r'^prefix/$', views.ExtendedUserUpdateAPIView.as_view(), name='basename-detail' ),
          )
      • Routers aniuats / Nested routers
        • drf-nested-routers
          • Installation
            • pip install drf-nested-routers
          • Usage: 
            • views.py
              • class NameserverViewSet(viewsets.ViewSet):
                   
                    def get_queryset(self):
                        domain_pk = self.kwargs.get('domain_pk', None)
                        if domain_pk:
                            nameservers = Nameserver.objects.filter(domain=domain_pk)
                        else:
                            nameervers = Nameserver.objects.all()
                        return nameservers

          • my_app/models.py
            my_app/urls.py
            my_app/views.py
            my_app/serializers.py
            ManyToMany
            class Primary(models.Model):
                field_1 = ...
             
            class Secondary(models.Model):
                primary = models.ForeignKey( PrimaryModel, related_name='secondaries')

                field_a = ...

            from rest_framework_nested import routers
            from my_app import views

            primary_router = routers.SimpleRouter()
            primary_router.register(r'primaries', views.PrimaryViewSet)

            secondary_router = routers.NestedSimpleRouter(primary_router, r'primaries', lookup='primary')
            secondary_router.register(r'users', views.SecondaryViewSet)

            urlpatterns = patterns('',  
                url(r'^', include(primary_router.urls)),
                url(r'^', include(secondary_router.urls)),  
            )

            from rest_framework import viewsets

            class PrimaryViewSet(viewsets.ModelViewSet):
                queryset = Primary.objects.all()
                serializer_class = PrimarySerializer

            class SecondaryViewSet(viewsets.ModelViewSet):
                queryset = Secondary.objects.all()
                serializer_class = SecondarySerializer

                def get_queryset(self):
                    # only entries that belong to specified parent
                    specified_primary_pk = int( self.kwargs.get('primary_pk', None) )
                    if specified_primary_pk:
                        secondaries = Poster.objects.filter(primary_id=specified_primary_pk)
                    else:
                        secondaries = Poster.objects.all()
                    return secondaries

                def perform_create(self, serializer):
                    # get the parent from the lookup field primary_pk
                    specified_primary_pk = int( self.kwargs.get('primary_pk', None) )
                    serializer.save(primary_id=specified_primary_pk)

            class PrimarySerializer(serializers.ModelSerializer):
                class Meta:
                    model = Primary

            class SecondarySerializer(serializers.ModelSerializer):
                class Meta:
                    model = Secondary


            OneToOne
            class PrimaryModel(models.Model):
                field_1 = ...
             
            class SecondaryModel(models.Model):
                primary = models.OneToOneField( PrimaryModel, related_name='secondary')

                field_a = ...

            primary_router = routers.SimpleRouter()
            primary_router.register(r'primaries', views.PrimaryViewSet)

            urlpatterns = patterns('',
                url(r'^', include(primary_router.urls)),
                url(r'^primaries/(?P<primary_id>[^/]+)/secondary/$', views.SecondaryView.as_view(), name='secondary-detail')
            )

            from rest_framework import mixins
            from rest_framework import generics

            class PrimaryViewSet(viewsets.ModelViewSet):
                queryset = Primary.objects.all()
                serializer_class = PrimarySerializer

            class CreateRetrieveUpdateDestroyAPIView(mixins.CreateModelMixin,
                                                     mixins.RetrieveModelMixin,
                                                     mixins.UpdateModelMixin,
                                                     mixins.DestroyModelMixin,
                                                     generics.GenericAPIView):
                """
                Concrete view for creating, retrieving, updating or deleting a model instance.
                """
                def post(self, request, *args, **kwargs):
                    return self.create(request, *args, **kwargs)

                def get(self, request, *args, **kwargs):
                    return self.retrieve(request, *args, **kwargs)

                def put(self, request, *args, **kwargs):
                    return self.update(request, *args, **kwargs)

                def patch(self, request, *args, **kwargs):
                    return self.partial_update(request, *args, **kwargs)

                def delete(self, request, *args, **kwargs):
                    return self.destroy(request, *args, **kwargs)

            class SecondaryView(CreateRetrieveUpdateDestroyAPIView):
                queryset = Secondary.objects.all()
                serializer_class = SecondarySerializer
                lookup_field = 'primary_id'
               
                def perform_create(self, serializer):
                    # get the parent from the lookup field primary_id (automatically created)
                    specified_primary_id = int( self.kwargs.get('primary_id', None) )
                    serializer.save(primary_id=specified_primary_id)

      • ...
    • Views
    • Parsers
      • Tutorial 1: Serialization
      • Exemples / Examples:
        • XMLParser
        • HierarchicalParser
        • XLSParser
        • Simple parser:
          • from rest_framework.compat import BytesIO
            from rest_framework.parsers import JSONParser

            stream = BytesIO(content)
            data = JSONParser().parse(stream)

            serializer = SnippetSerializer(data=data)
            if serializer.is_valid():
                ...
        • MultiPartJSONParser
        • Multipart parser, adapted to work with PHP
          • my_project/settings.py
            • REST_FRAMEWORK = {
                  'DEFAULT_PARSER_CLASSES': (
                      'rest_framework.parsers.JSONParser',
                      'my_app.parsers.PhpMultiPartParser',
                  ),
              )

          • my_app/parsers.py
            • import re

              from rest_framework import parsers
              from django.http.request import QueryDict
              from django.utils.datastructures import MultiValueDict

              def compact_dataandfiles(dataandfiles):
                  """
                  Convert a DataAndFiles structure of type:
                     data: {"field1[0]":"value1", "field1[0]":"value2"}
                  to:
                     data: {"field1":["value1","value2"] }
                  """
                  compact_data_qd = QueryDict('', mutable=True)
                  regex = re.compile(r"\[.*\]")
                  tmp_mdict = MultiValueDict()

                  for key, valuelist in dataandfiles.data._iterlists():
                      # remove "[...]" from key
                      compact_key = regex.sub(r"", key)
                      if compact_key in compact_data_qd:
                          tmp_mdict.setlist(compact_key, valuelist)
                          compact_data_qd.update(tmp_mdict)
                      else:
                          compact_data_qd.setlist(compact_key, valuelist)
                  # build a new DataAndFiles with the same files and compact data
                  compact_dataandfiles = parsers.DataAndFiles(compact_data_qd, dataandfiles.files)
                 
                  return compact_dataandfiles

              class PhpMultiPartParser(parsers.MultiPartParser):
                  """
                  MultiPartParser that accepts requests with data of syntax:
                  "field1[0]":"value1", "field1[0]":"value2", e.g. from PHP
                  """
                  def parse(self, stream, media_type=None, parser_context=None):
                      dataAndFiles = parsers.MultiPartParser.parse(self, stream, media_type=media_type, parser_context=parser_context)
                      dataAndFiles = compact_dataandfiles(dataAndFiles)
                      return dataAndFiles
        • Parse content from uploaded file (from additional button in admin)
        • Parse content from uploaded file (with curl):
          • class TotoViewSet(viewsets.ModelViewSet):
                queryset = TotoModel.objects.all()
                serializer_class = TotoSerializer
                permission_classes = (permissions.IsAuthenticated,)
               
                renderer_classes = (BrowsableAPIRenderer, JSONRenderer, CSVRenderer)
               
                parser_classes = (JSONParser, MultiPartParser,)
               
                def create(self, request, *args, **kwargs):
                    data = None
                   
                    # data is sent as direct json (literally or from file: @) (JSONParser):
                   
            # curl -X POST -H "Content-Type:application/json" -u admin:admin http://127.0.0.1:8000/totos/ -d '{"a":"b"}'
                    # curl -X POST -H "Content-Type:application/json" -u admin:admin http://127.0.0.1:8000/totos/ -d @toto.json
                    if request._content_type == 'application/json':
                        data = request.DATA

                    # data is sent inside a file that is uploaded (
            MultiPartParser):
                    # curl -X POST -H "Content-Type:multipart/form-data" -u admin:admin http://127.0.0.1:8000/totos/ -F "file=@toto.json;type=application/json"
                    # note: if type is not specified, it defaults to "application/octet-stream"
                    else:
                        print request.FILES['file']
                        fitxer = File(request.FILES['file'])
                        content = fitxer.read()
                        stream = BytesIO(content)
                        if request.FILES['file'].content_type == 'application/json':
                            data = JSONParser().parse(stream)
                        else:
                            print "No parser for content type: %s" % (request.FILES['file'].content_type)
                            errors = dict()
                            errors['error'] =  "No parser for content type: %s" % (request.FILES['file'].content_type)
                            return Response(errors,status=status.HTTP_400_BAD_REQUEST)
                        fitxer.close()
                   
                    # serialize the data:
                    serializer = self.get_serializer(data=data, files=request.FILES, many=True)
                   
                    # if all is fine, save the object in the database:
                    if serializer.is_valid():
                        self.pre_save(serializer.object)
                        self.object = serializer.save(force_insert=True)
                        self.post_save(self.object, created=True)
                        return Response(serializer.data, status=status.HTTP_201_CREATED)
                    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    • Serialització / Serialization
      • Serializers
        • depth
        • Dealing with multiple objects
        • Dynamically modifying fields
          • class ParticipantSerializer(serializers.ModelSerializer):
                def __init__(self, *args, **kwargs):
                    ...
                    # Don't pass the 'fields' arg up to the superclass
                    fields = kwargs.pop('fields', None
            )
                    ...
          • class ParticipantSerializer(serializers.ModelSerializer):
                gcmdevice_registration_id = serializers.SlugRelatedField(many=True, slug_field='registration_id', required=False)
                apnsdevice_registration_id = serializers.SlugRelatedField(many=True, slug_field='registration_id', required=False)

                def __init__(self, *args, **kwargs):     
                    dades = kwargs.get('data')
                    # si s'ha fet un post
                    if dades:
                        if 'gri' in dades:
                            # Don't pass the 'gri' arg up to the superclass
                            gri = dades.pop('gri', None)
                   
                            if gri:
                                # crea una llista amb un sol element
                                llista = [gri]
                                # posa aquesta llista com si s'hagués passat per paràmetre
                                kwargs.get('data')['gcmdevice_registration_id'] = llista

                    # Instantiate the superclass normally
                    super(ParticipantSerializer, self).__init__(*args, **kwargs)
               
                class Meta:
                    model = Participant
                    fields = ('id','username','password','first_name','last_name', 'email', 'nif', 'phone', 'apnsdevice_registration_id', 'gcmdevice_registration_id')

      • Fields
        • Boolean
          • Default value
            • Model defaults ignored on empty text field or empty boolean field. #1101
            • Solution
              • models.py
                • class MyModel(models.Model):
                  is_something = models.BooleanField(default=True)

              • serializers.py
                • class MyModelSerializer(serializers.ModelSerializer):
                      # need to specify default=True because default value from model is not taken
                      is_something = serializers.BooleanField(default=True)

      • Basic example
        • serializers.py
          • from rest_framework import serializers

            from my_app.models import MyModel

            class MyModelSerializer(serializers.ModelSerializer):
                class Meta:
                    model = MyModes
                    fields = ('field1','field2',) 
                 

      • No serialització de camps buits / Do not serialize empty fields
        • serializers.py
          • from rest_framework import serializers
            from rest_framework.fields import SkipField

            class NonNullSerializer(serializers.ModelSerializer):
                """
                Do not serialize empty fields.
                """

                def to_representation(self, instance):
                    """
                    Object instance -> Dict of primitive datatypes.
                    http://stackoverflow.com/questions/27015931/remove-null-fields-from-django-rest-framework-response
                    """
                    ret = OrderedDict()
                    fields = [field for field in self.fields.values() if not field.write_only]

                    for field in fields:
                        try:
                            attribute = field.get_attribute(instance)
                        except SkipField:
                            continue

                        if (attribute is not None) and not (isinstance(attribute, (basestring)) and len(attribute)==0):
                            represenation = field.to_representation(attribute)
                            if represenation is None:
                                # Do not seralize empty objects
                                continue
                            if isinstance(represenation, list) and not represenation:
                                # Do not serialize empty lists
                                continue
                            ret[field.field_name] = represenation

                    return ret

            class MyModelSerializer(NonNullSerializer):
                ...


      • Serialitzadors diferents a l'entrada (request) i a la sortida (response) / Different serializers for input (request) and output (response)
        • get_serializer_class(self)
          • views.py
            • class MyModelViewSet(viewsets.ModelViewSet):
                  serializer_class = MyModelSerializer

                  # used by overriden get_serializer_class
                  input_serializer_class = MyModelInputSerializer

                  def get_serializer_class(self):
                      if self.request.method == 'GET':
                          return self.serializer_class
                      else:
                          return self.input_serializer_class
          • serializers.py
            • class MyModelInputSerializer(serializers.ModelSerializer):
              ...
              class MyModelSerializer(serializers.ModelSerializer):
              ...

        • Python rest framwork: different serializers for input and output of service?
        • read_only=True
        • write_only=True
        • get_response()
          • serializer_class specifies the request serializer and we explicitely define the response by calling the response serializer
          • views.py (from django_rest_auth)
            • class LoginView(GenericAPIView):
                  """
                  Check the credentials and return the REST Token
                  if the credentials are valid and authenticated.
                  Calls Django Auth login method to register User ID
                  in Django session framework

                  Accept the following POST parameters: username, password
                  Return the REST Framework Token Object's key.
                  """
                  permission_classes = (AllowAny,)
                  serializer_class = LoginSerializer
                  token_model = Token
                  response_serializer = TokenSerializer

                  def login(self):
                      self.user = self.serializer.validated_data['user']
                      self.token, created = self.token_model.objects.get_or_create(
                          user=self.user)
                      if getattr(settings, 'REST_SESSION_LOGIN', True):
                          login(self.request, self.user)

                  def get_response(self):
                      return Response(
                          self.response_serializer(self.token).data, status=status.HTTP_200_OK
                      )

                  def post(self, request, *args, **kwargs):
                      self.serializer = self.get_serializer(data=self.request.data)
                      self.serializer.is_valid(raise_exception=True)
                      self.login()
                      return self.get_response()
        • @detail_route
          • serializer_class specifies the response serializer. Request serializer is not needed: we directly use request.data.get(...)
          • my_app/serializers.py
            • class MyResponseSerializer(serializers.Serializer):
                 
              output_field_1 = serializers.CharField()
          • my_app/views.py
            • from my_app.serializers import MyResponseSerializer

              Class MyModelViewSet(viewsets.ModelViewSet):
                  ...
                  @detail_route(methods=['post'], serializer_class = MyResponseSerializer,  permission_classes = (permissions.IsAuthenticated,))
                  def my_function(self, request, *args, **kwargs):
                      """
                      My description.
                      ---
                      response_serializer: MyResponseSerializer
                      parameters_strategy:
                          form: replace
                      omit_parameters:
                          - query
                      """
                     
                      input_field_1 = request.data.get('input_field_1','my_default')


                      # build temporary object
                      obj = dict()
                      obj['output_field_1'] = ...
                     
                      # serialize it
                      serializer = self.get_serializer(obj)
                     
                      return Response(serializer.data)

      • Canvi de nom del camp / Change name of the field
        • solves problem: '...' object has no attribute 'new_field_name'
        • new_field_name = serializers.IntegerField(source='existing_field_name')
        • new_field_name = serializers.ModelField(model_field=MyModel()._meta.get_field('existing_field_name'))
        • new_field_name = OtherSerializer(source='existing_field_name')
        • new_field_name = serializers.SlugRelatedField(slug_field='existing_field_name',source='fk_field',queryset=Parent.objects.all())
      • Paraules reservades com a camps / Reserved words as fields
      • Serializers and choices
        • Note: serializers.ChoiceField does not use display_name for json (serialization/deserialization); only for forms
        • If you want to serialize the associated text in choices, instead of the value, see bellow:
        • models.py
          • class MyModel(models.Model):
                MY_FIELD_CHOICES = (
                                   (0,"zero"),
                                   (1,"one"),
                                   (2,"two"),
                                   )
                my_field = models.IntegerField(choices=MY_FIELD_CHOICES)

        • Option 1: only works for serialization
          • serializers.py
            • class MyModelSerializer(serializers.ModelSerializer):
                  my_field = serializers.CharField(source='get_my_field_display')
                 
                  class Meta:
                      model = MyModel
                      fields = ('my_field',)
        • Option 2: works for serialization and deserialization
          • serializers.py
            • from rest_framework import serializers

              class EnhancedChoiceField(serializers.ChoiceField):
                  """
                  Serializer for choice field.
                  """
                 
                  # serialization
                  def to_representation(self, obj):
                      return self.choices[obj]

                  # deserialization
                  # NOTE: does not work with i18n values
                  def to_internal_value(self, data):
                      if data.isdigit():
                          return int(data)
                      else:
                          for key,val in self.choices.iteritems():
                              if val==data:
                                  return key
                      raise serializers.ValidationError("Not a valid choice. Valid choices are: {}".format(self.choices.values()) )

              class
              MyModelSerializer(serializers.ModelSerializer):
                  my_field = EnhancedChoiceField(choices=MyModel.
              MY_FIELD_CHOICES, required=False)
                  class Meta:
                      model = MyModel
                      fields = ('my_field',)
          • views.py (needed by drf-swagger) (type: replaces serializer; parameters: replaces form)
            • class MyModelView(...):
                  """
                  View for my_model
                  ---
                  # type: replaces the serializer ("Response Class") (all fields must be specified)
                  # parameters: overwrites the form ("Parameters") (only changed fields need to be specified)
                  xxx:
                      type:
                          my_field:
                             
              description: "Some description here"
                              required: true
                                          type: choice
                                          enum:
                                  - zero
                                  - one
                                  - two

                      parameters:
                          - name: my_field
                            description: "Some description here"
                            required: true
                            paramType: form
                            type: choice
                            enum:
                                - zero
                                - one
                                - two
                  """
                  ...

      • Create and perform_create, update and perform_update (see also Views)
        • rest_framework/mixins.py rest_framework/serializers.py rest_framework/serializers.py
          my_app/views.py

          my_app/serializers.py
          class CreateModelMixin(object):
          class MyModelViewSet(viewsets.ModelViewSet):
              # usually not overwritten
              def create(self, request, *args, **kwargs):
                  serializer = self.get_serializer(data=request.data)
                  serializer.is_valid(raise_exception=True)
                  self.perform_create(serializer)
                  headers = self.get_success_headers(serializer.data)
                  return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

              # function to be overwritten in my_app/views.py
              def perform_create(self, serializer):
                  # explicit call to save is the bare minimum
                  serializer.save()



          class BaseSerializer(Field):
              ...
                  def save(self, **kwargs):
                      ...
                      else:
                           self.instance = self.create(validated_data)



          class ModelSerializer(Serializer):
          class MyModelSerializer(serializers.ModelSerializer):
              # overwritten e.g. to deal with two serializations at the same time (FK)      
              def create(self, validated_data):
                  # essentially:          
                  return self.Meta.model.objects.create(**validated_data)

              class Meta:
                  model = MyModel


          class MyModelSerializer(serializers.ModelSerializer):
              def create(self, validated_data):
                  # if instance already exists, return it
                  instance, created = self.Meta.model.objects.get_or_create(**validated_data)
                  return instance

              class Meta:
                  model = MyModel
          class UpdateModelMixin(object):
          class MyModelViewSet(viewsets.ModelViewSet):
              """
              Update a model instance.
              """
              # usually not overwritten
              def update(self, request, *args, **kwargs):
                  partial = kwargs.pop('partial', False)
                  instance = self.get_object()
                  serializer = self.get_serializer(instance, data=request.data, partial=partial)
                  serializer.is_valid(raise_exception=True)
                  self.perform_update(serializer)
                  return Response(serializer.data)

              # function to be overwritten in my_app/views.py
              def perform_update(self, serializer):
                  # explicit call to save is the bare minimum
                  serializer.save()

              def partial_update(self, request, *args, **kwargs):
                  kwargs['partial'] = True
                  return self.update(request, *args, **kwargs)




          class BaseSerializer(Field):
              ...
              def save(self, **kwargs):
                  ...
                  if self.instance is not None:
                      self.instance = self.update(self.instance, validated_data)




          class ModelSerializer(Serializer):
          class MyModelSerializer(serializers.ModelSerializer):
              # overwritten e.g. to deal with two serializations at the same time (FK)      
              def update(self, instance, validated_data):
                      ...


          class ModelSerializer(Serializer):
          class TaggitSerializer(serializers.Serializer):
          class MyModelSerializer(TaggitSerializer, serializers.ModelSerializer):
              # overwritten e.g. to deal with two serializations at the same time (FK)      
              def update(self, instance, validated_data):
                  ...
                  # update mymodel own fields and tags
                  # no need to call serializers.ModelSerializer.update because it is called from TaggitSerializer.update
                  updated_instance = TaggitSerializer.update(self, instance, validated_data)
                  ...    
                  return updated_instance

          from django.shortcuts import get_object_or_404
          from django.http import Http404

          class MyModelViewSet(viewsets.ModelViewSet):
              """
              Create (or update) a model instance.
              """
              def create(self, request, *args, **kwargs):
                  # check if the instance already exists
                  instance = None
                  # get parameter from data
                  #name = request.data.get('name',None)
                  try:
                      queryset = self.get_queryset()
                      if name:
                          filter_kwargs = dict()
                          #filter_kwargs['name'] = name
                          instance = get_object_or_404(queryset, **filter_kwargs)
                  except Http404:
                      pass

                  if instance:
                      # update it
                      #self.kwargs['name'] = name # set parameter to kwargs (url in nested viewsets)
                      return self.update(request, *args, **kwargs)
                  else:
                      # create a new instance
                      serializer = self.get_serializer(data=request.data)
                      serializer.is_valid(raise_exception=True)
                      self.perform_create(serializer, remove_overlapped=remove_overlapped)
                      headers = self.get_success_headers(serializer.data)
                      return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)



          class MyModelViewSet(viewsets.ModelViewSet):
              """
              Update (or create) a model instance.
              """
              def update(self, request, *args, **kwargs):      
                  partial = kwargs.pop('partial', False)

                  # check if the instance already exists
                  instance = None
                  # get parameter from kwargs (url in nested viewsets)
                  #name = kwargs.get('name', None)
                  try:
                      instance = self.get_object()
                  except Http404:
                      pass
                 
                  if instance:
                      # update it
                      serializer = self.get_serializer(instance, data=request.data, partial=partial)
                      serializer.is_valid(raise_exception=True)
                      self.perform_update(serializer, remove_overlapped=remove_overlapped)
                      return Response(serializer.data)
                  else:
                      # create it           
                      #request.data['name'] = name # set parameter to data
                      return self.create(request, *args, **kwargs)


      • Serializers relations
        • camp / field
          mostra / shows
          read
          (serialization)
          write
          (deserialization)
          args
          info
          comments
          • 2.x
            • Field(source='function_in_model_class')
          • 3.x
            • fields( ..., 'function_in_model_class')
          output from function defined in models.Model x
          -



          Field(source='fk_field_name.field')
          field from fk
          x
          -

          direct FK (class where FK is declared)

          WritableField

          .to_native(self, obj) .from_native(self, data)
          you have to implement .from_native(self,value), .to_native(self,value)
          ModelField






          RelatedField
          __unicode__
          x
          -



          CustomRelatedField

          .to_native(self, obj)
          .to_representation(self, obj)
          .from_native(self, data)
          .to_internal_value(self, data)



          SerializerMethodField('function_in_serializer')
          def function_in_serializer(self,obj)
              qs=...objects.filter(...)
              serializer=...Serializer(qs)
              return serializer.data
          output from function defined in serializers.ModelSerializer
          (aniuat i filtrat / nested and filtered)
          x
          -



          PrimaryKeyRelatedField
          pk
          x
          x

          reverse FK (some class points to here using a fk)
          write: can be used to attach a child to its parent via a FK
          HyperlinkedRelatedField
          url_to_items



          reverse FK
          tracks = SlugRelatedField(slug_field='title')
          slug_field
          x
          x
          slug_field
          direct FK, reverse FK
          • write: can be used to attach a child to its parent via a slug_field
          • in models.py, slug_field has to be unique=True
          HyperlinkedIdentityField
          url_to_set





          OtherSerializer (many=True)
          (nested)
          x
          x


          • write: can be used to recursively add childs
        • Writable foreign/one2one keys

          • my_project/urls.py



            urlpatterns = patterns('',

                url(r'^admin/', include(admin.site.urls)),
               
                # djangorestframework auth
                url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),

                # swagger documentation
                url(r'^docs/', include('rest_framework_swagger.urls')),
               
                url(r'^v1/api/', include('library.urls', namespace='v1')),
                #url(r'^v1/api/', include('my_app_2.urls', namespace='v1')),
                #url(r'^v1/api/', include('my_app_3.urls', namespace='v1')),  
            )


            library/models.py
            library/urls.py
            library/views.py
            library/serializers.py
            from django.db import models

            class ISBN(models.Model):
                number = models.IntegerField()

                def __unicode__(self):
                    return u'%s' % (self.number)

            class Publisher(models.Model):
                name = models.CharField( max_length=100 )
                address = models.TextField( blank=True, null=True )

                def __unicode__(self):
                    return u'%s' % (self.name)

            class Author(models.Model):
                first_name = models.CharField( max_length=100 )
                last_name = models.CharField( max_length=100 )

                def __unicode__(self):
                    return u'%s %s' % (self.first_name, self.last_name)

            class Book(models.Model):
                isbn = models.OneToOneField( ISBN )
                publisher = models.ForeignKey( Publisher, related_name='books', blank=True, null=True )
                authors = models.ManyToManyField( Author, related_name='books', blank=True, null=True )
               
                title = models.CharField( max_length=200 )

                def __unicode__(self):
                    return u'%s' % (self.title)
               
            class Comment(models.Model):
                book = models.ForeignKey( Book, related_name='comments' )
               
                text = models.TextField()

            from django.conf.urls import patterns, include, url

            #from rest_framework import routers
            from rest_framework_nested import routers
            from library import views

            book_router = routers.SimpleRouter()
            book_router.register(r'books', views.BookViewSet)

            comment_router = routers.NestedSimpleRouter(book_router, r'books', lookup='book')
            comment_router.register(r'comments', views.CommentViewSet)

            urlpatterns = patterns('',  
                url(r'^', include(book_router.urls)),
                url(r'^', include(comment_router.urls)),
            )

            from django.shortcuts import render
            from django.shortcuts import get_object_or_404

            from rest_framework import viewsets
            from rest_framework import status
            from rest_framework.response import Response
            from rest_framework.exceptions import APIException

            from models import Book, Comment
            from serializers import BookSerializer, CommentSerializer

            class BookViewSet(viewsets.ModelViewSet):
                queryset = Book.objects.all()
                serializer_class = BookSerializer


            class CommentViewSet(viewsets.ModelViewSet):
                queryset = Comment.objects.all()
                serializer_class = CommentSerializer

                def get_queryset(self):
                    # only entries that belong to specified parent
                    book_pk = self.kwargs.get('book_pk', None)
                    book = get_object_or_404(Book, pk=book_pk)
                    comment = Comment.objects.filter(book=book)
                    return comment

                def perform_create(self, serializer):
                    # deal with exceptions
                    try:
                        # try something
                    except Exception as e:
                        # raise Http404
                        raise APIException("Error: %s" % e)

                    # get the parent from the lookup field book_pk
                    book_pk = self.kwargs.get('book_pk', None)
                    book = get_object_or_404(Book, pk=book_pk)
                    serializer.save(book=book)

            from rest_framework import serializers

            from models import Book, ISBN, Publisher, Comment, Author

            class ISBNSerializer(serializers.ModelSerializer):
                class Meta:
                    model = ISBN
                    fields = ('number',)

            class PublisherSerializer(serializers.ModelSerializer):
                class Meta:
                    model = Publisher
                    fields = ('name','address',)

            class CommentSerializer(serializers.ModelSerializer):
                class Meta:
                    model = Comment
                    fields = ('text',)

            class AuthorSerializer(serializers.ModelSerializer):
                class Meta:
                    model = Author
                    fields = ('first_name','last_name',)

            class BookSerializer(serializers.ModelSerializer):       
                isbn = ISBNSerializer()
                publisher = PublisherSerializer()
                authors = AuthorSerializer( many=True )
                comments = CommentSerializer( many=True, read_only=True )
               
                def create(self, validated_data):
                    if 'isbn' in validated_data:
                        # create the ISBN object
                        isbn_data = validated_data.pop('isbn')
                        isbn = ISBN.objects.create(**isbn_data)
                        # relate it to the book
                        validated_data['isbn'] = isbn
                       
                    if 'publisher' in validated_data:
                        publisher_data = validated_data.pop('publisher')
                        publisher, publisher_created = Publisher.objects.get_or_create(**publisher_data)
                        validated_data['publisher'] = publisher
                       
                    authors_list = None
                    if 'authors' in validated_data:
                        authors_data_list = validated_data.pop('authors')
                        authors_list = []
                        for author_data in authors_data_list:
                            author, author_created = Author.objects.get_or_create(**author_data)
                            authors_list.append(author)
                           
                    book = Book.objects.create(**validated_data)
                    if authors_list:
                        book.authors.add(*authors_list)
                       
                    return book
               
                def update(self, instance, validated_data):
                    if 'isbn' in validated_data:
                        # extract information about the ISBN object
                        isbn_data = validated_data.pop('isbn')
                        # get the isbn object related to this book
                        isbn = instance.isbn
                        # update it
                        for attr, value in isbn_data.items():
                            setattr(isbn, attr, value)
                        isbn.save()
                       
                    if 'publisher' in validated_data:
                        # extract information about the publisher object
                        publisher_data = validated_data.pop('publisher')
                        # get the publisher object related to this book
                        publisher = instance.publisher
                        # update it
                        for attr, value in publisher_data.items():
                            setattr(publisher, attr, value)
                        publisher.save()
                       
                       
                    authors_list = None
                    if 'authors' in validated_data:
                        authors_data_list = validated_data.pop('authors')
                        authors_list = []
                        for author_data in authors_data_list:
                            # TODO: other than add
                            #   Best solution: nested router?
                            # TODO: if get returns more than one object
                            author, author_created = Author.objects.get_or_create(**author_data)
                            authors_list.append(author)

                    # update book own fields
                    updated_instance = serializers.ModelSerializer.update(self, instance, validated_data)

                    # add the authors (m2m)
                    if authors_list:
                        updated_instance.authors.add(*authors_list)
               
                    return updated_instance


                class Meta:
                    model = Book
                    fields = ('title','isbn','publisher','authors','comments',)

            from django.conf.urls import patterns, include, url

            from rest_framework import routers
            from library import views

            book_router = routers.SimpleRouter()
            book_router.register(r'books', views.BookViewSet)

            urlpatterns = patterns('',  
                url(r'^', include(book_router.urls)),
                url(r'^books/(?P<pk>[^/]+)/comments/$', views.BookCommentsCreateRetrieveView.as_view(), name='bookcomments-detail'),
            )
            from django.shortcuts import render
            from django.shortcuts import get_object_or_404

            from rest_framework import viewsets
            from rest_framework import status
            from rest_framework.response import Response

            from models import Book, Comment
            from serializers import BookSerializer, CommentSerializer

            class BookViewSet(viewsets.ModelViewSet):
                queryset = Book.objects.all()
                serializer_class = BookSerializer

            class CreateRetrieveAPIView(mixins.CreateModelMixin,
                                        mixins.RetrieveModelMixin,
                                        generics.GenericAPIView):
                """
                Concrete view for creating or retrieving a model instance.
                """
                def post(self, request, *args, **kwargs):
                    return self.create(request, *args, **kwargs)

                def get(self, request, *args, **kwargs):
                    return self.retrieve(request, *args, **kwargs)


            class BookCommentsCreateRetrieveView(CreateRetrieveAPIView):
                queryset = Comment.objects.all()
                serializer_class = CommentSerializer
             
                def get_queryset(self):
                    # only entries that belong to specified parent
                    book_pk = self.kwargs.get('book_pk', None)
                    book = get_object_or_404(Book, pk=book_pk)
                    comment = Comment.objects.filter(book=book)
                    return comment

                def perform_create(self, serializer):
                    # get the parent from the lookup field book_pk
                    book_pk = self.kwargs.get('book_pk', None)
                    book = get_object_or_404(Book, pk=book_pk)
                    serializer.save(book=book)




          • my_project/urls.py



            from django.conf.urls import patterns, include, url
            from django.contrib import admin

            urlpatterns = patterns('',
                # admin
                url(r'^admin/', include(admin.site.urls)),
               
                # djangorestframework auth
                url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),

                # swagger documentation
                url(r'^docs/', include('rest_framework_swagger.urls')),
               
                url(r'^v1/api/', include('my_app.urls', namespace='v1')),
                #url(r'^v1/api/', include('my_app_2.urls', namespace='v1')),
                #url(r'^v1/api/', include('my_app_3.urls', namespace='v1')),
            )



            my_app/models.py
            my_app/urls.py
            my_app/views.py
            serializers.py

            from django.db import models from my_app import views from rest_framework import viewsets
            from rest_framework import serializers
            ForeignKey
            class ModelA(models.Model):
                field_a_1 = models.CharField( max_length=100 )
               
            class ModelB(models.Model):
                model_a = models.ForeignKey( ModelA, related_name='models_b', blank=True, null=True )
               
                field_b_1 = models.CharField( max_length=100 )

            from rest_framework import routers

            model_a_router = routers.SimpleRouter()
            model_a_router.register(r'models_a', views.ModelAViewSet)

            model_b_router = routers.SimpleRouter()
            model_b_router.register(r'models_b', views.ModelBViewSet)

            urlpatterns = patterns('',
                url(r'^', include(model_a_router.urls)),
                url(r'^', include(model_b_router.urls)),
            )
            from my_app.models import ModelA, ModelB
            from my_app.serializers import ModelASerializer, ModelBSerializer

            class ModelBViewSet(viewsets.ModelViewSet):
                queryset = ModelB.objects.all()
                serializer_class = ModelBSerializer

            class ModelAViewSet(viewsets.ModelViewSet):
                queryset = ModelA.objects.all()
                serializer_class = ModelASerializer

            from my_app.models import ModelA, ModelB

            class ModelBSerializer(serializers.ModelSerializer):
                class Meta:
                    model = ModelB
                    fields = ('field_b_1',)
                   
            class ModelASerializer(serializers.ModelSerializer):
                models_b = ModelBSerializer( many=True )
               
                class Meta:
                    model = ModelA
                    fields = ('field_a_1','models_b',)
            from rest_framework_nested import routers

            model_a_router = routers.SimpleRouter()
            model_a_router.register(r'models_a', views.ModelAViewSet)

            model_b_router = routers.NestedSimpleRouter(model_a_router, r'models_a', lookup='model_a')
            model_b_router.register(r'models_b', views.ModelBViewSet)

            urlpatterns = patterns('',
                url(r'^', include(model_a_router.urls)),
                url(r'^', include(model_b_router.urls)),
            )
            from django.shortcuts import get_object_or_404

            class ModelBViewSet(viewsets.ModelViewSet):
                queryset = ModelB.objects.all()
                serializer_class = ModelBSerializer
               
                def get_queryset(self):
                    # only entries that belong to specified parent
                    model_a_pk = self.kwargs.get('model_a_pk', None)
                    model_a = get_object_or_404(ModelA, pk=model_a_pk)
                    models_b = ModelB.objects.filter(model_a=model_a)
                    return models_b

                def perform_create(self, serializer):
                    # get the parent from the lookup field dashboard_pk
                    model_a_pk = self.kwargs.get('model_a_pk', None)
                    model_a = get_object_or_404(ModelA, pk=model_a_pk)
                    serializer.save(model_a=model_a)

            class ModelAViewSet(viewsets.ModelViewSet):
                queryset = ModelA.objects.all()
                serializer_class = ModelASerializer
            from my_app.models import ModelA, ModelB

            class ModelBSerializer(serializers.ModelSerializer):
                class Meta:
                    model = ModelB
                    fields = ('field_b_1',)
                   
            class ModelASerializer(serializers.ModelSerializer):
                models_b = ModelBSerializer( many=True )
               
                class Meta:
                    model = ModelA
                    fields = ('field_a_1','models_b',)
            OneToOne
            class ModelC(models.Model):   
                field_c_1 = models.CharField( max_length=100 )
               
            class ModelD(models.Model):
                model_c = models.OneToOneField( ModelC, related_name='model_d', blank=True, null=True )
               
                field_d_1 = models.CharField( max_length=100 )
            from rest_framework import routers

            model_c_router = routers.SimpleRouter()
            model_c_router.register(r'models_c', views.ModelCViewSet)

            model_d_router = routers.SimpleRouter()
            model_d_router.register(r'models_d', views.ModelDViewSet)

            urlpatterns = patterns('',
                url(r'^', include(model_c_router.urls)),
                url(r'^', include(model_d_router.urls)),
            )
            from my_app.models import ModelC, ModelD
            from my_app.serializers import ModelCSerializer, ModelDSerializer

            class ModelCViewSet(viewsets.ModelViewSet):
                queryset = ModelC.objects.all()
                serializer_class = ModelCSerializer

            class ModelDViewSet(viewsets.ModelViewSet):
                queryset = ModelD.objects.all()
                serializer_class = ModelDSerializer

            from my_app.models import ModelC, ModelD

            class ModelCSerializer(serializers.ModelSerializer):       
                class Meta:
                    model = ModelC
                    fields = ('field_c_1',)

            class ModelDSerializer(serializers.ModelSerializer):       
                model_c = ModelCSerializer( many=False )
               
                class Meta:
                    model = ModelD
                    fields = ('field_d_1','model_c',)

            ...

            ManyToMany
            class ModelE(models.Model):   
                field_e_1 = models.CharField( max_length=100 )

            class ModelF(models.Model):   
                models_e = models.ManyToManyField( ModelE, related_name='models_f', blank=True, null=True )
               
                field_f_1 = models.CharField( max_length=100 )

            from rest_framework import routers

            model_e_router = routers.SimpleRouter()
            model_e_router.register(r'models_e', views.ModelEViewSet)

            model_f_router = routers.SimpleRouter()
            model_f_router.register(r'models_f', views.ModelFViewSet)

            urlpatterns = patterns('', 
                url(r'^', include(model_e_router.urls)),
                url(r'^', include(model_f_router.urls)),
            )
            from my_app.models import ModelE, ModelF
            from my_app.serializers import ModelESerializer, ModelFSerializer

            class ModelEViewSet(viewsets.ModelViewSet):
                queryset = ModelE.objects.all()
                serializer_class = ModelESerializer

            class ModelFViewSet(viewsets.ModelViewSet):
                queryset = ModelF.objects.all()
                serializer_class = ModelFSerializer
            from my_app.models import ModelE, ModelF

            class ModelFSerializer(serializers.ModelSerializer):       
                class Meta:
                    model = ModelF
                    fields = ('field_f_1',)

            class ModelESerializer(serializers.ModelSerializer):       
                models_f = ModelFSerializer( many=True )
               
                class Meta:
                    model = ModelE
                    fields = ('field_e_1','models_f',)

            ...

          • Writable Foreign keys in Django Rest Framework
          • Writable nested serialization (3.x)
            • models.py
              • class MyModel(models.Model)
                    field_1 = ...
                    field_2 = ...

                class MyModelSon(models.Model)
                    mymodel = models.OneToOneField(MyModel, related_name='son')
                    field_a = ...
            • serializers.py
              • field is not writable:
                • class MyModelSonSerializer(serializers.ModelSerializer):
                      class Meta:
                          model = MyModelSon

                  class MyModelSerializer(serializers.ModelSerializer):
                      son = MyModelSonSerializer( read_only=True )

                      class Meta:
                          model = MyModel
                          fields = ('field_1', 'field_2', 'son')
              • field is writable,
                •  and required:
                  • class MyModelSonSerializer(serializers.ModelSerializer):
                        class Meta:
                            model = MyModelSon

                    class MyModelSerializer(serializers.ModelSerializer):
                        son = MyModelSonSerializer()

                        class Meta:
                            model = MyModel
                            fields = ('field_1', 'field_2', 'son')

                        def create(self, validated_data):
                            son_data = validated_data.pop('son')
                            my_instance = MyModel.objects.create(**validated_data)
                            MyModelSon.objects.create(mymodel=my_instance, **son_data)
                            return my_instance
                • but not required:
                  • class MyModelSonSerializer(serializers.ModelSerializer):
                        class Meta:
                            model = MyModelSon

                    class MyModelSerializer(serializers.ModelSerializer):
                        son = MyModelSonSerializer( required=False )

                        class Meta:
                            model = MyModel
                            fields = ('field_1', 'field_2', 'son')

                        def create(self, validated_data):
                            if 'son' in validated_data:
                                son_data = validated_data.pop('son')
                                my_instance = MyModel.objects.create(**validated_data)
                                MyModelSon.objects.create(mymodel=my_instance, **son_data)
                            else:
                               
                    my_instance = MyModel.objects.create(**validated_data)
                            return my_instance
      • Problemes / Problems:
        • AttributeError at ...
          'QuerySet' object has no attribute '...'
          • Solució / Solution
            • Comproveu que la crida al serialitzador la feu amb / Check that the call to the serializer contains:
              • many=True
      • Exemples / Examples:
        • WritableField
          • Hexadecimal integers
            • class HexIntegerField(serializers.WritableField):
                  # serialization
                  def to_native(self, obj):
                      return "0x%02x" % (obj)

                  # deserialization
                  def from_native(self, data):
                      # convert from hex in unicode ("0x02") to integer (2)
                      return int(data, 16)

        • SerializerMethodField
          • simple field from grandparent (single field from parent can be done with SlugRelatedField):
            • models.py
              • ...
            • serializers.py
              • class TotoSerializer(serializers.ModelSerializer):
                    grandparent_field = serializers.SerializerMethodField()
                   
                    def get_grandparent_field(self,obj):
                        my_int_field = obj.parent.grandparent.some_int_field
                        return serializers.IntegerField().to_representation(my_int_field)
                   
                    class Meta:
                        model = TotoModel
                        fields = ('grandparent_field',)

          • make date calculations:
            • class ServiceEITSerializer(serializers.ModelSerializer):
                  events = EventEITSerializer()
                 
                  # drf 2.x:
                 
              #start_time = serializers.SerializerMethodField('get_start_time')
                 
              #end_time = serializers.SerializerMethodField('get_stop_time')
                  # drf 3.x:
                  start_time = serializers.SerializerMethodField()
                  end_time = serializers.SerializerMethodField()
                
                  def get_start_time(self,obj):
                      min_date = min([event.start for event in obj.events.all()])
                      # drf 2.x:
                      #return serializers.DateTimeField().to_native(min_date)
                      # drf 3.x:
                      return serializers.DateTimeField().to_representation(min_date)


                  def get_stop_time(self,obj):
                      max_date = max([event.start + datetime.timedelta(hours=event.duration.hour,minutes=event.duration.minute,seconds=event.duration.second) for event in obj.events.all()])
                      # drf 2.x:
                      #
              return serializers.DateTimeField().to_native(max_date)
                      # drf 3.x:
                      return serializers.DateTimeField().to_representation(max_date)
                    
                  class Meta:
                      model = DVB_Service
                      fields = ('service_id','start_time','end_time','events',)
          • aniuat i filtrat / nested and filtered (ManyToMany)
            • How can I apply a filter to a nested resource in Django REST framework?
            • models.py
              • class TotoFill(models.Model):
                    nom = models.CharField(max_length=100)
                    start_date = models.DateField()
                    end_date = models.DateField()

                    def __unicode__(self):
                        return self.nom

                    class Meta:
                        ordering = ['-start_date']

                class TotoPare(models.Model):
                    titol = models.CharField(max_length=100)
                    toto_fills = models.ManyToManyField(TotoFill)
                    start_date = models.DateField()
                    end_date = models.DateField()
                   
                    class Meta:
                        ordering = ['-start_date']
            • serializers.py
              • class TotoFillSerializer(serializers.ModelSerializer):
                    class Meta:
                        model = TotoFill

                class TotoPareSerializer(serializers.ModelSerializer):
                    toto_fillets = serializers.
                SerializerMethodField('toto_fills_no_futurs')
                   
                    def toto_fills_no_futurs(self, obj):
                        # "totopare" exists because it is automatically created (ManyToMany through)
                        qs = TotoFill.objects.filter(totopare=obj,start_date__lte=date.today())
                        serializer = TotoFillSerializer(qs, many=True)
                        return serializer.data
                   
                    class Meta:
                        model = TotoPare
                        fields = ('id','titol','start_date','end_date','toto_fillets')
        • SlugRelatedField
          • see SerializerMethodField to get grandparent fields instead of parent fields -> Avi/Grandparent
          • models.py (Note: Pare means Father; Fill means Son)
            • class Pare(models.Model):
                  pare_id = models.IntegerField(_("pare_id"), unique=True)

              class Fill(models.Model):
                  pare = models.ForeignKey(Pare, related_name="fills", blank=True, null=True)
                  fill_id = models.IntegerField(_("fill_id"))

          • option 1: preserving names
            • serializers.py
              • class FillSerializer(serializers.ModelSerializer):
                    pare = serializers.SlugRelatedField(slug_field='pare_id',queryset=Pare.objects.all())
                   
                    class Meta:
                        model = Fill
                        fields = ('pare','fill_id',)
            • can be used to create an object by sending a json via POST:
              • {
                    "pare": 813,
                    "fill_id": 1008
                }
          • option 1.2: groups and users
            • serializers.py
              • from django.contrib.auth.models import Group, User

                class UserSerializer(serializers.ModelSerializer):
                    groups = serializers.SlugRelatedField( many=True, slug_field='name', queryset=Group.objects.all() )
                   
                    class Meta:
                        model = User
            • views.py
              • class UserViewSet(viewsets.ModelViewSet):
                    queryset = User.objects.all()
                    serializer_class = UserSerializer

                    def perform_create(self, serializer):
                        user = serializer.save()
                        # set the password
                        user.set_password(serializer.validated_data['password'])
                        user.save()

          • option 2: changing the name of the serialized field ('pare'->'toto'; it could be 'pare_id' instead of 'toto')
            • serializers.py
              • class FillSerializer(serializers.ModelSerializer):
                    toto = serializers.SlugRelatedField(slug_field='pare_id', source='pare', queryset=Pare.objects.all()
                )
                   
                    class Meta:
                        model = DVB_Event
                        fields = ('toto','fill_id',)
            • can be used to create an object by sending a json via POST:
              • {
                    "toto": 813,
                    "fill_id": 1008
                }
          • Avi / Grandparent
            • models.py
              • class Avi(models.Model):
                    avi_id =
                models.IntegerField(_("avi_id"), unique=True)
                class Pare(models.Model):
                    avi =
                models.ForeignKey(Avi, related_name="pares", blank=True, null=True)
                    pare_id = models.IntegerField(_("pare_id"), unique=True)
                class Fill(models.Model):
                    pare = models.ForeignKey(Pare, related_name="fills", blank=True, null=True)
                    fill_id = models.IntegerField(_("fill_id"))
            • serializers.py
              • class FillSerializer(serializers.ModelSerializer):
                    avi = serializers.SlugRelatedField(slug_field='avi_id', source='pare.avi')
                   
                    class Meta:
                        model = Fill
                        fields = ('avi','fill_id',)
    • Views





      • custom action



























        @detail_route()
        def custom_action
        @list_route()
        def custom_action
        @detail_route(methods=['POST', 'DELETE'])
        def custom_action




        attributes methods
        (that can be defined)

        get(self, request)
        post(self, request) get
        put, patch delete

        (specified by "methods")

        inside the table, the actions provided by:

        generics.GenericAPIView
        • model
        • queryset
        • serializer_class
        • permission_classes
        • lookup_field
        • paginate_by
        • ...


        +
        get







        =
        ListAPIView generics

        post






        CreateAPIView


        get





        RetrieveAPIView



        put, patch




        UpdateAPIView




        delete



        DestroyAPIView
        get
        post






        ListCreateAPIView


        get
        put, patch



        RetrieveUpdateAPIView


        get

        delete


        RetrieveDestroyAPIView


        get
        put, patch delete


        RetrieveUpdateDestroyAPIView








        MyAPIView
        viewsets.GenericViewSet +
        list
        create(self, request, *args, **kwargs) retrieve
        update, partial_update
        delete



        =
        ModelViewSet viewsets
        list

        retrieve





        ReadOnlyModelViewSet
        (x) (x) (x) (x) (x) (x)
        (x)
        MyViewSet


        provided actions
        (that can be overwritten) ->

        def list(self, request): def create(self, request): def retrieve(self, request, pk=None): def update(self, request, pk=None):
        def partial_update(self, request, pk=None):
        def destroy(self, request, pk=None):









        ListModelMixin CreateModelMixin RetrieveModelMixin UpdateModelMixin DestroyModelMixin









        mixins








      • +
        mixins.ViewSetMixin(object)
        • as_view()
        • initialize_request()
        mixins.CreateModelMixin(object)
        • def create(self, request, *args, **kwargs):
            serializer = self.get_serializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            self.perform_create(serializer)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
        • def perform_create(self, serializer):
            serializer.save()
        • def get_success_headers(self,data):
            ...
        mixins.RetrieveModelMixin(object)
        • def retrieve(self, request, *args, **kwargs):
            instance = self.get_object()
            serializer = self.get_serializer(instance)
            return Response(serializer.data)
        mixins.ListModelMixin(object)
        • def list(self, request, *args, **kwargs):
            queryset = self.filter_queryset(self.get_queryset())

            page = self.paginate_queryset(queryset)
            if page is not None:
              serializer = self.get_serializer(page, many=True)
              return self.get_paginated_response(serializer.data)

            serializer = self.get_serializer(queryset, many=True)
            return Response(serializer.data)
        mixins.UpdateModelMixin(object)
        • def update(self, request, *args, **kwargs):
            partial = kwargs.pop('partial', False)
            instance = self.get_object()
            serializer = self.get_serializer(instance, data=request.data, partial=partial)
            serializer.is_valid(raise_exception=True)
            self.perform_update(serializer)
            return Response(serializer.data)
        • def perform_update(self, serializer):
            serializer.save()
        • def partial_update(self, request, *args, **kwargs):
            kwargs['partial'] = True
            return self.update(request, *args, **kwargs)update
        mixins.DestroyModelMixin(object)
        • def destroy(self, request, *args, **kwargs):
              instance = self.get_object()
              self.perform_destroy(instance)
              return Response(status=status.HTTP_204_NO_CONTENT)  
        • def perform_destroy(self, instance):
                  instance.delete()
        =
        views.APIView(View)
        • renderer_classes
        • parser_classes
        • authentication_classes
        • throttle_classes
        • permission_classes
        • content_negotiation_class
        • metadata_class
        • versioning_class
        • as_view()
        • ...
        generics.GenericAPIView(views.APIView)


        x



        generics.CreateAPIView(mixins.CreateModelMixin,
                            GenericAPIView):
        • post(self, request, *args, **kwargs):
            self.create


        x



        generics.RetrieveAPIView(mixins.RetrieveModelMixin,
                              GenericAPIView):
        • get(self, request, *args, **kwargs):
            self.retrieve(request, *args, **kwargs)



        x


        generics.ListAPIView(mixins.ListModelMixin,
                          GenericAPIView):
        • get(self, request, *args, **kwargs):
            self.list




        x

        generics.UpdateAPIView(mixins.UpdateModelMixin,
                            GenericAPIView):
        • put(self, request, *args, **kwargs):
            self.update
        • patch(self, request, *args, **kwargs):
            self.partial_update





        x
        generics.DestroyAPIView(mixins.DestroyModelMixin,
                             GenericAPIView):
        • delete(self, request, *args, **kwargs):
            self.destroy

        x

        x


        generics.ListCreateAPIView(mixins.ListModelMixin,
                                mixins.CreateModelMixin,
                                GenericAPIView):
        • get(self, request, *args, **kwargs):
            self.list
        • post(self, request, *args, **kwargs):
            self.create


        x

        x

        generics.RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
                                    mixins.UpdateModelMixin,
                                    GenericAPIView):
        • get(self, request, *args, **kwargs):
            self.retrieve
        • put(self, request, *args, **kwargs):
            self.update
        • patch(self, request, *args, **kwargs):
            self.partial_update


        x


        x
        generics.RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
                                     mixins.DestroyModelMixin,
                                     GenericAPIView):
        • get(self, request, *args, **kwargs):
            self.retrieve
        • delete(self, request, *args, **kwargs):
            self.destroy


        x

        x
        x
        generics.RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
                                           mixins.UpdateModelMixin,
                                           mixins.DestroyModelMixin,
                                           GenericAPIView):
        • get(self, request, *args, **kwargs):
            self.retrieve
        • put(self, request, *args, **kwargs):
            self.update
        • patch(self, request, *args, **kwargs):
            self.partial_update
        • delete(self, request, *args, **kwargs):
            self.destroy






        CustomAPIView(GenericAPIView):
        • # e.g.: view for django-solo
        • get(self, request, *args, **kwargs):
            ...
        • post(self, request, *args, **kwargs):
            ...
        • put(self, request, *args, **kwargs):
            ...
        • patch(self, request, *args, **kwargs):
            ...
        • delete(self, request, *args, **kwargs):
            ...
        viewsets.GenericViewSet(ViewSetMixin,
                                                  generics.GenericAPIView):
            pass
        x
        x
        x
        x
        x
        viewsets.ModelViewSet(mixins.CreateModelMixin,
                           mixins.RetrieveModelMixin,
                           mixins.UpdateModelMixin,
                           mixins.DestroyModelMixin,
                           mixins.ListModelMixin,
                           GenericViewSet):

        x
        x


        viewsets.ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
                                   mixins.ListModelMixin,
                                   GenericViewSet):





        CustomViewSet(...,
                                 GenericViewSet):

        viewsets.ViewSet(ViewSetMixin,
                                    views.APIView):
            pass







      • APIView
        • It can be used, e.g., to perform an action, not directly related to the classical creation of database entries
        • APIView does not have serializer_class; generics.GenericAPIVIew has it (and swagger can show it on docs)
        • MyAPIView (based on generics.GenericAPIView) examples ((?) when using plain GenericAPIView, no get, post, get, put, patch are implemented, and need to be defined):
          • class TotoAPIView(generics.GenericAPIView):
                # input serializer
                serializer_class = TotoInputSerializer
               
                # define post method
                def post(self, request, format=None):
                    input_serializer = TotoInputSerializer(data=request.DATA)
                    if input_serializer.is_valid():
                        ...
                        return ...
                    else:
                        return Response(input_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
          • class TotoAPIView(generics.GenericAPIView):
                # output serializer
                serializer_class = TotoOutputSerializer

                # define get method
                # this could also be achieved by using generics.ListAPIView (no need to define get, then)
                def get(self, request, *args, **kwargs):
                    queryset = self.get_queryset()
                    serializer = self.get_serializer(queryset, many=True)
                    return Response(serializer.data)
          • class TotoAPIView(generics.GenericAPIView):
                # output serializer
                serializer_class = TotoOutputSerializer

                # define get method
                # this could also be achieved by using generics.ListAPIView (no need to define get, then)
                def get(self, request, *args, **kwargs):
                    # build temporary object
                    obj = dict()
                    obj['
            first_numer'] = MyModel.objects.count()
                    obj['second_numer'] = MyModel.objects.filter(is_active=True).count()

                    # serialize it
                    serializer = self.get_serializer(obj)
                   
                    return Response(serializer.data)

          • class OtherModelSerializer(serializers.ModelSerializer):
                ...

            class MixedOutputSerializer(serializers.Serializer):
               
            first_number = serializers.IntegerField()
                results = OtherModelSerializer(many=True)
          • class TotoAPIView(generics.GenericAPIView):
                # output serializer (just for swagger, because recursive serialization does not work with mixed ORM and non-ORM)
                serializer_class = MixedOutputSerializer

                # define get method
                # this could also be achieved by using generics.ListAPIView (no need to define get, then)
                def get(self, request, *args, **kwargs):
                    other_queryset = OtherModel.objects.all()
                    other_serializer = OtherModelSerializer(queryset, many=True)

                    return Response({'first_number':
            MyModel.objects.count(),
                                     'results':other_serializer.data})
        • Example with django-solo
          • my_project/settings.py
            • INSTALLED_APPS = (
                  ...
                  'solo',
              )

          • my_app/models.py
            • from solo.models import SingletonModel

              class PricingLimits(SingletonModel):
                  ...

          • my_app/serializers.py
            • class MySoloModelSerializer(serializers.ModelSerializer):
                  class Meta:
                      model = MySoloModel


          • my_app/views.py
            • class MySoloModelView(generics.GenericAPIView):
                  serializer_class = MySoloModelSerializer

                  def get(self, request):
                      instance = MySoloModel.get_solo()
                      serializer = self.get_serializer(instance)
                      return Response(serializer.data)

          • my_app/urls.py
            • url(r'^my_view/$', views.MySoloModelView.as_view(), name='mysolomodel-detail' ),
      • ViewSets
        • See general View table
        • ViewSets are recommended if you are going to use a router
        • If you are not happy with predefined ModelViewSet (list, create, retrieve, update, destroy) or ReadOnlyModelViewSet (list, retrieve), you can define your own ViewSet by combining mixins, that provide the desired features. For instance, you can define CreateUpdateViewSet with only create and update.
        • ModelViewSet examples:
          • from rest_framework import viewsets, permissions

            class ParticipantChallengeViewSet(viewsets.ModelViewSet):
                """
                Relationship (and status) between participants and challenges.
                """
                queryset = ParticipantChallenge.objects.all()
                serializer_class = ParticipantChallengeSerializer
                permission_classes = (permissions.IsAuthenticated,)
                # search by <challenge> (specified on the url) instead of <pk>
                lookup_field = 'challenge'
               
                # as participant pk is not explicitly provided,
                # get it from the user on the request
                def pre_save(self, obj):
                    obj.participant = self.request.user
               
                # for list: filter by the name of the participant
                def get_queryset(self):
                    p = self.request.user
                    #return p.challenge_set.all()
                    return ParticipantChallenge.objects.filter(participant=p)

        • MyViewSet (based on user-constructed ViewSet) examples (with a usage example):
          • class CreateViewSet(mixins.CreateModelMixin,
                                viewsets.GenericViewSet):
                pass
          • class CreateUpdateViewSet(mixins.CreateModelMixin,
                                      mixins.UpdateModelMixin,
                                      viewsets.GenericViewSet):
                pass
          • class CreateRetrieveUpdateViewSet(mixins.CreateModelMixin,
                                              mixins.RetrieveModelMixin,
                                              mixins.UpdateModelMixin,
                                              viewsets.GenericViewSet):
                pass
          • class UpdateViewSet(mixins.UpdateModelMixin,
                                viewsets.GenericViewSet):
                pass
          • class ListUpdateViewSet(mixins.ListModelMixin,
                                    mixins.UpdateModelMixin,
                                    viewsets.GenericViewSet):
                pass
          • class ListViewSet(mixins.ListModelMixin,
                              viewsets.GenericViewSet):
                pass
          • # example of usage of user-defined viewsets:
            class TotoCreateUpdateViewSet(CreateUpdateViewSet):
                queryset = Toto.objects.all()
                serializer_class = TotoSerializer

                # overwrite create method
                def create(self, request, *args, **kwargs):
                    serializer = self.get_serializer(data=request.DATA, files=request.FILES)
                   
                    if serializer.is_valid():
                        self.pre_save(serializer.object)
                       
                        #set_password
                        serializer.object.set_password(serializer.data['password'])
                       
                        self.object = serializer.save(force_insert=True)
                        self.post_save(self.object, created=True)

        • Mixins

          • mixin