Django

Índex

Django

  • Django

  • Alternatives
  • Instal·lació / Installation
    • using pyenv
      1. install pyenv
      2. cd ~/src
      3. create virtualenv:
        • pyenv virtualenv 3.10 mysite-3.10
      4. temporarily use created virtualenv
        • pyenv shell mystite-3.10
      5. install django
        • pip install django
      6. create django project (estructura de directoris)
      7. set pyenv permanently:
        • cd mysite
        • pyenv local mysite-3.10
    • 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
      • Django x
        • Django 1.8 Django >= 1.10
          mydir/myproject/settings.py
          • INSTALLED_APPS = [
                "polls",
            ]
          mydir/myproject/settings.py
          • INSTALLED_APPS = [
                "polls.apps.PollsConfig",
            ]
          urls.py
          mydir/myapp/urls.py
          • from . import views

          mydir/myapp/views.py
          • from .models import Question


        • ...
      • Django 4
        • Django < 4 Django 4
          NullBooleanField() BooleanField(null=True)


        • ...
      • Django 3
        • Django < 3 Django 3
          from django.utils import six import six
          from django.shortcuts import render_to_response
          (removed) use render() with request as additional parameter
        • ModuleNotFoundError: No module named 'moneyed.localization'
          • ...
        • ...
      • Django 2
        • Django 2.0 release notes
        • on_delete for related fields is now mandatory
          • on_delete is a required positional argument for ForeignKeys, even in migrations
          • on_delete in migrations
            • option 1: modify all migration files (using perl and lookahead regex)
              • perl -pi.bak -e 's/models.ForeignKey\((?!.*on_delete)/models.ForeignKey\(on_delete=models.deletion.CASCADE, /g' myapp/migrations/*.py
              • perl -pi.bak -e 's/models.OneToOneField\((?!.*on_delete)/models.OneToOneField\(on_delete=models.deletion.CASCADE, /g' myapp/migrations/*.py
            • option 2:squash migrations
              1. squash your migrations
              2. add explicit on_delete in related fields (ForeignKey, OneToOneField) (in migration squashed file and models.py)
                • on_delete=models.CASCADE # default value, no migration will be created
              3. delete old migrations (when migrations have been applied to all your instances)
        • settings.MIDDLEWARE_CLASSES -> settings.MIDDLEWARE (introduced in 1.10)
        • ImportError: cannot import name 'allow_lazy' from 'django.utils.functional'
        • AttributeError: module 'django.contrib.gis.db.models' has no attribute 'GeoManager'
        • ModuleNotFoundError: No module named 'django.core.urlresolvers'
        • <class 'myapp.admin.MyModelAdmin'>: (admin.E012) There are duplicate field(s) in 'fieldsets[15][1]'.
          • ...
        • django.core.exceptions.ImproperlyConfigured: Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.
          • in included urls.py, add
            • app_name = 'myapp'

        • Django 1 Django 2
          Simplified URL routing syntax from django.conf.urls import include, url

          urlpatterns = [
              url(r'^admin/', include(admin.site.urls)),
          from django.conf.urls import include
          from django.urls import path

          urlpatterns = [
              path('admin/', admin.site.urls),


          url(r'^rest-auth/', include('dj_rest_auth.urls')), path('rest-auth/', include('dj_rest_auth.urls')),

          url(r'^mypath/', include('myapp.urls', namespace='mynamespace')), path('mypath/', include('myapp.urls')),
          and myapp/urls.py:
          app_name = "mynamespace"
          django.core.exceptions.ImproperlyConfigured:
          Specifying a namespace in include() without providing an app_name is not supported.
          Set the app_name attribute in the included module,
          or pass a 2-tuple containing the list of patterns and app_name instead.
          tracker super(MyClass, self).save(*args, **kwargs)
          my_field_has_changed = self.tracker.has_changed('my_field')
          my_field_has_changed = self.tracker.has_changed('my_field')
          super(MyClass, self).save(*args, **kwargs)
          # this would return False:
          #my_field_has_changed = self.tracker.has_changed('my_field')


          user.is_anonymous()
          user.is_authenticated()
          # from Django 1.10
          user.is_anonymous
          user.is_authenticated


          dataandfiles.data._iterlists() dataandfiles.data.lists()

          # allow_lazy is deprecated from Django 1.10
          from django.utils.functional import allow_lazy

          def my_function(...)

          my_function = allow_lazy(my_function, six.text_type, SafeText)
          from django.utils.functional import keep_lazy

          def my_function(...)

          my_function = keep_lazy(six.text_type, SafeText)(my_function)

        • Problemes / Problems
          • Error: database connection isn't set to UTC
            • Solució / Solution
              • pip install 'psycopg2<2.9'
          • ImportError: Module "django.contrib.auth.middleware" does not define a "SessionAuthenticationMiddleware" attribute/class
        • TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use mymodel.set() instead.
        • ValueError: callable 'xxx' is not supported by signature
          • when using allow_lazy instead of keep_lazy (with different syntax)
        • TypeError: __init__() got an unexpected keyword argument 'name'
        • ...
      • Biblioteques / Libraries



        • 1.x 2.x 3.x 4.x

          rtd git 1.11 2.2 3.2
          dj-rest-auth

          1.1.0 1.1.0
          2.2.8
          3.0.0 (rest_register: 204 instead of 201)
          4.0.1 (rest_register: 204 instead of 201)
          6.0.0
          master: registration/views.py
          x
          django-activity-stream

          0.6.3 0.10.0
          1.4.1
          2.0.0 1.4.0
          2.0.0
          django-admin-csvexport

          1.11 2.2 2.2
          django-allauth
          x 0.40.0 0.40.0
          0.54.0
          0.63.6
          django-appconf

          1.0.3 1.0.3
          1.0.6
          -
          -
          django-autoslug

          1.9.8 1.9.8
          1.9.9
          1.9.9
          django-axes

          5.3.3 5.3.3
          5.27.0
          6.4.0
          django-cors-headers

          2.1.0 2.1.0
          3.11.0
          4.3.1
          django-countries

          5.1.1 5.1.1
          7.6.1
          7.6.1
          django-extra-fields

          2.0.5 2.0.5
          3.0.2
          3.0.2
          django-filter

          2.2.0 2.2.0
          21.1
          23.5
          django-ipware

          2.1.0 2.1.0
          4.0.2
          7.0.1 (?)
          -
          django-json-widget

          1.0.0 1.0.0
          2.0.1
          2.0.1
          django-jsonfield-backport

          - 1.0.5 -
          django-mass-edit

          3.2.0 3.2.0
          3.5.0
          3.5.0
          django-model-utils

          3.0.0 3.0.0
          4.0.0
          4.1.0 (breaking changes)
          4.2.0
          4.5.1
          django-modeltranslation

          0.14.4 0.14.4
          0.18.2
          0.18.11
          django-modeltree

          - 0.5 0.5
          django-money

          2.0.3 2.0.3
          3.5.3
          3.5.2
          django-nested-inline

          0.3.7 0.3.7
          0.4.6
          0.4.6
          django-notifications-hq

          1.4.0
          1.5
          1.4.0
          1.5.0
          1.7.0 (depends on django-model-utils>=3.1.0)
          1.8.3
          django-otp

          0.9.4 1.1.6 1.5.0
          django-paypal

          0.3.6 2.1 2.1
          django-solo

          1.1.5 1.2.0
          2.0.0
          2.2.0
          django-sslserver

          0.19 0.19
          0.22
          0.22
          django-stdimage

          2.4.2 2.4.2
          5.3.0
          6.0.2 (ImportError: cannot import name 'Resampling' from 'PIL.Image' => needs pillow>=9.5.0
          This package has been deprecated in favor of django-pictures.
          6.0.2
          django-storages

          1.9.1 1.9.1
          1.12.3 (bucket -> bucket_name)
          1.14.3
          django-taggit

          1.3.0 1.3.0
          1.5.1
          2.1.0 (RelatedManager.set)
          4.0.0
          djangorestframework

          3.11 3.11.0
          3.13.1
          3.15.1
          djangorestframework-csv

          2.1.0 2.1.0
          3.0.2
          3.0.2
          djangorestframework-gis

          0.15 0.15
          1.0
          1.0
          djangorestframework-jwt

          1.10.0 1.10.0
          1.11.0
          1.11.0
          drf-extensions

          0.4.0
          0.5.0
          0.4.0
          0.7.1
          0.7.1
          drf-nested-routers

          0.91 0.91
          0.93.4
          0.94.0
          drf-yasg

          1.17.1
          1.20.0
          1.17.1
          1.21.7
          1.21.7
      • Django 1.10, 1.11

        • last version that
          supports Django 1.11
          notes
          django-allauth 0.40.0
          dj-rest-auth 1.1.0
          django-activity-stream 0.8.0 0.6.3 0.8.0 is not enough for Django2: ModuleNotFoundError: No module named 'jsonfield_compat': you will need 0.10.0 and install django-jsonfield-backport
          django-autoslug 1.9.8
          django-axes 5.3.3 File "[...]/lib/python3.8/site-packages/axes/checks.py", line 75, in axes_middleware_check
              if "axes.middleware.AxesMiddleware" not in settings.MIDDLEWARE:
          TypeError: argument of type 'NoneType' is not iterable
          When upgrading to django-axes v5:
          WARNINGS:
          ?: (axes.W002) You do not have 'axes.middleware.AxesMiddleware' in your settings.MIDDLEWARE.
          ?: (axes.W003) You do not have 'axes.backends.AxesBackend' in your settings.AUTHENTICATION_BACKENDS.
                  HINT: AxesModelBackend was renamed to AxesBackend in django-axes version 5.0.
          django-filter 2.2.0 Migration guide (to 2.0); 2.0 is the minimum for Django 2.x
          • Filter.name -> Filter.field_name
          • _0, _1 -> _after, _before; _min, _max
            • or overwrite widget, to keep using _0, _1:
              • import django_filters

                class OldRangeWidget(django_filters.widgets.RangeWidget):
                    suffixes = ["0", "1"]

                class MyIsoDateTimeRangeField(django_filters.fields.IsoDateTimeRangeField):
                    widget = OldRangeWidget
                   
                class MyIsoDateTimeFromToRangeFilter(django_filters.RangeFilter):
                    field_class = MyIsoDateTimeRangeField
          django-mass-edit 3.2.0
          django-money 2.0.3
          django-modeltranslation 0.14.4
          django-notifications-hq 1.5 1.5: breaking changes
          django-solo 1.2.0
          django-stdimage

          django-storages 1.9.1
          django-taggit 1.3.0
          djangorestframework 3.11
          drf-extensions 0.5.0
          drf-nested-routers ?
          drf-yasg 1.20.0 ?
        • settings
          • # even for DEBUG
            ALLOWED_HOSTS = ['*']
          • #MIDDLEWARE_CLASSES = (...)
            MIDDLEWARE = [...]
        • namespaces must be unique
        • patterns
          • old style:
            • from django.conf.urls import patterns, include, url

              urlpatterns = patterns('',
                  url(r'^', ...
              )
          • new style:
            • urlpatterns = [
                  url(r'^', ...
              ]
        • deprecated: get_all_field_names
          • # LogEntry._meta.get_all_field_names()
            [f.name for f in LogEntry._meta.get_fields()]
        • django_filters
          • "__init__() got an unexpected keyword argument 'request'"
            • old
              • class MyModelFilterSet(django_filters.FilterSet):
                    def __init__(self, data=None, queryset=None, prefix=None, strict=None):
            • new
              • class MyModelFilterSet(django_filters.FilterSet):
                    def __init__(self, data=None, queryset=None, prefix=None, strict=None, request=None):
      • Django 1.9
      • Django 1.7 release notes
      • Django 1.6 release notes
    • Migració de python 2 a 3 / Python 2 to 3 migration
      • Python 3
      • Issues
      • Paquets / Packages

        • last version that
          supports Python 2.7
          comments
          Django 1.11.29
          django-allauth 0.40.0
          django-rest-auth 0.9.5 should be replaced by dj-rest-auth (but it does not support Python2)
          dj-rest-auth 0.1.1 Not working on Python2
          django-axes 4.5.4
          django-stdimage 2.4.2 we cannot use StdImageFieldFile with django-stdimage <4.1.0, because when myinstance.refresh_from_db() is called
          (e.g. explicitly from tests, implicitly from api serializers), it raises an error: "RuntimeError: maximum recursion depth exceeded".
          But django-stdimage >=4.1.0, even if it is installed from python2.7, has python3 syntax (e.g. call to super without parameters)
          django-storages 1.9.1
          django-taggit 0.24.0
          djangorestframework 3.9.4
          drf-extensions 0.4.0
          drf-nested-routers 0.91
          drf-yasg 1.17.1
          futures 3.3.0 not needed by Python3; required by google-...
          ruamel.ordereddict 0.4.15 not needed by Python3
          ruamel.yaml 0.16.13
          ruamel.yaml.clib 0.2.2
          ...

      • ...
    • Tutorials
    • Django 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
      • Performance and optimization
      • 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-admin
        • export PYTHONPATH="/absolute/path/to/mysite:$PYTHONPATH"
        • django-admin ... --settings=mysite.settings
        • export DJANGO_SETTINGS_MODULE=mysite.settings
          django-admin ...
      • django-admin and manage.py
        • custom manage commands
          • Writing custom django-admin commands
            • Exemple / Example
              • my_app/management/commands/my_command.py
                • import logging
                  from argparse import RawTextHelpFormatter

                  from django.core.management.base import BaseCommand

                  logger = logging.getLogger(__name__)

                  class Command(BaseCommand):
                      def create_parser(self, prog_name, subcommand):
                          """
                          Override create_parser, to add formatter class that allows newlines
                          """
                          parser = BaseCommand.create_parser(self, prog_name, subcommand)
                          parser.formatter_class = RawTextHelpFormatter
                          return parser

                      help = """
                  My command help
                  with newlines.
                  """

                      def add_arguments(self, parser):
                          parser.add_argument('--my-first-arg',
                                              required=True,
                                              help=("First argument"))
                     
                      def handle(self, *args, **options):
                          print("options: {}".format(options))
                          my_first_arg_value = options['my_first_arg']

                          # verbose -> logger level
                          # https://docs.djangoproject.com/en/dev/ref/django-admin/#cmdoption-v
                          # when not specified, default value for options["verbosity"] is 1
                          logger_level = {
                              0: logging.ERROR,
                              1: logging.WARNING,
                              2: logging.INFO,
                              3: logging.DEBUG,
                          }.get(options["verbosity"], logging.WARNING)
                          logger.setLevel(logger_level)

                          # sample logger messages
                          logger.error("[handle] error")
                          logger.warning("[handle] warning")
                          logger.info("[handle] info")
                          logger.debug("[handle] debug")

                          ...
                          self.stdout.write(self.style.SUCCESS('Successfully executed command'))
              • call from command:
                • ./manage my_app my_command --my-first-arg='tata'
              • call from unit test (How to Unit Test a Django Management Command):
                • from django.core.management import call_command

                  call_command("my_command", "--my-first-arg=tata")
          • ...
      • Models and databases
      • ...
    • 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)



    • step 1: install django step 2: create django project and use it from eclipse




      option 1: create django project from cli + use eclipse option 2: create django project from eclipse
      Python

      structure
      python (virtualenv) from cli create django project from cli
      use Eclipse
      create django project from Eclipse
      sytem-wide python

      1. [python], project
      • src/
        • mysite/
          • manage.py
          • mysite/
            • __init__.py
            • settings.py
            • urls.py
            • wsgi.py

      • cd src
      • django-admin startproject mysite
      • New / Project...
      • PyDev Project (to import an existing Django project, not an Eclipse project yet)
      • Project Name: mysite
      • Directory: src/mysite (autocompleted)
      • Django Version: 1.4 or later
      • Properties -> PyDev-PYTHONPATH -> String Substitution Variables -> Add variable
        • DJANGO_MANAGE_LOCATION: manage.py (this will allow project to be run/debug as Django)
        • DJANGO_SETTINGS_MODULE: my_project.settings
      • New / Project...
      • PyDev Django Project
      • Project Name: mysite
      • Directory: src/mysite (autocompleted)
      • Django Version: 1.4 or later
      2. app
      • src/
        • mysite/
          • ...
          • myapp/
            • admin.py
            • apps.py
            • __init__.py
            • models.py
            • tests.py
            • views.py
            • migrations/
              • __init__.py

      • cd src
      • cd mysite
      • ./manage.py startapp myapp
      • Django -> Create application: myapp

      virtualenv inside project


      1. python virtualenv
      • src/
        • mysite/
          • env/
            • ...
      • cd src
      • mkdir mysite
      • virtualenv env
      • source env/bin/activate
      • pip install --upgrade pip
      • pip install django


      2. project
      • src/
        • mysite/
          • env/
            • ...
          • manage.py
          • mysite
            • __init__.py
            • settings.py
            • urls.py
            • wsgi.py

      • cd src
      • cd mysite
      • django-admin startproject mysite .
      • New / Project...
      • PyDev Project
      • Project Name: mysite
      • Directory: src
      • Interpreter: ~/src/mysite/env/bin/python

      3. app




      virtualenv in pyenv 1. python virtualenv
      • ~/.pyenv/
        • versions/
          • mysite-3.10/
      • pyenv virtualenv 3.10 mysite-3.10
      • pyenv shell mysite-3.10
      • pip install django



      2. project (method a)
      • src/
        • mysite/
          • manage.py
          • mysite/
            • __init__.py
            • settings.py
            • urls.py
            • wsgi.py
            • awsgi.py

      • cd src
      • mkdir mysite
      • cd mysite
      • pyenv local mysite-3.10
      • django-admin startproject mysite .
      • New / Project...
      • PyDev Project (to import an existing Django project, not an Eclipse project yet)
      • Project Name: mysite
      • Directory: src
      • Interpreter: mysite-3.10
        (segurament l'haureu de crear: Click here to configure an interpreter not listed)
      • (select project): PyDev -> Set as Django Project
      • Properties -> PyDev-PYTHONPATH -> String Substitution Variables -> Add variable
        • DJANGO_MANAGE_LOCATION: manage.py (this will allow project to be run/debug as Django)
        • DJANGO_SETTINGS_MODULE: my_project.settings
      • New / Project...
      • PyDev Django Project
      • Project Name: mysite
      • Directory: src
      • Interpreter: mysite-3.10
        (segurament l'haureu de crear: Click here to configure an interpreter not listed)
      • (Properties are already set)

      2. project (method b)
      • src/
        • mydir/
          • manage.py
          • mysite_b/
            • __init__.py
            • settings.py
            • urls.py
            • wsgi.py
            • awsgi.py (django>=3)

      • cd src
      • mkdir mydir
      • cd mydir
      • pyenv local mydir-3.10
      • django-admin startproject mysite_b .
      • New / Project...
      • PyDev Project (to import an existing Django project, not an Eclipse project yet)
      • Project Name: mydir
      • Directory: .../src/mydir
      • Interpreter: mydir-3.10
        (segurament l'haureu de crear: Click here to configure an interpreter not listed)
      • this will create:
        • .project: to indicate that this is an Eclipse project
        • .pydevproject: to indicate that this is a PyDev project
      • (select project): PyDev -> Set as Django Project
        • this will activate Django menu
        • will add to .project: <nature>org.python.pydev.django.djangoNature</nature>
      • Properties -> PyDev-PYTHONPATH -> String Substitution Variables -> Add variable
        • DJANGO_MANAGE_LOCATION: manage.py (this will allow project to be run/debug as Django)
          • if not set, it will be prompted with a default value of manage.py
            the first time we run a Django custom command (e.g. check)
        • DJANGO_SETTINGS_MODULE: mysite_b.settings (needed bacause default value is: mydir.settings)


      3. app




  • 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
            • How to execute a Python script from the Django shell?
            • standalone.py
              • # -*- coding: utf-8 -*-
                import django

                from django.conf import settings
                settings.configure()
                django.setup()

                ...

              • # -*- coding: utf-8 -*-
                import sys, os, django
                #sys.path.append('/path/to/my_project')
                sys.path.append(os.getcwd())
                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 %}
  • Desplegament / Deployment
  • Exemple mínim amb una vista POST simple / Minimal example with a simple POST view:
    • create dir for project
      • mkdir -p ~/src/myproject; cd ~/src/myproject
    • install python3 in a virtualenv
      • virtualenv-3.5 env
      • source env/bin/activate
      • pip install --upgrade pip
    • install django
      • pip install django
    • create django project:
      • django-admin startproject mysite .
    • create application
      • ./manage.py startapp myapp
    • mysite/urls.py
      • from django.urls import include, path

        urlpatterns = [
            path('myapp/', include('myapp.urls')),
        ]
    • myapp/urls.py
      • from django.urls import path

        from . import views

        app_name = 'myapp'
        urlpatterns = [
            path('toto/', views.toto, name='toto'),
        ]
    • myapp/views.py
      • from django.http import HttpResponse
        from django.views.decorators.csrf import csrf_exempt

        @csrf_exempt
        def toto(request):
            try:
                primer = request.POST['primer']
            except:
                return HttpResponse("missing POST parameter: primer", content_type="text/plain")
            return HttpResponse("primer: {0}".format(primer), content_type="text/plain")
    • ./manage runserver
    • curl -i -X POST http://localhost:8000/myapp/toto/ -F primer=2
  • Exemples mínims / Minimal examples


    • project
      application



      access

      manage settings urls.py application urls.py models admin application views.py html template ./manage.py runserver
      hello world view
      • create dir
        • cd ~/src
        • mkdir -p mydir; cd mydir
      • install python3 in a virtualenv
        • virtualenv env
        • source env/bin/activate
        • pip install --upgrade pip
      • install django
        • pip install "django<3"
      • create django project:
        • django-admin startproject myproject .
      • create application
        • ./manage.py startapp myapp

      mydir/myproject/urls.py
      • from django.urls import path, include

        urlpatterns = [
            path('myapp/', include('myapp.urls')),
        ]
      mydir/myapp/urls.py
      • from django.urls import path

        from . import views

        urlpatterns = [
            path('', views.index, name='index'),
        ]


      mydir/myapp/views.py
      • from django.http import HttpResponse

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

      • http://127.0.0.1:8000/myapp/
      view with parameters from url
      • create dir
        • cd ~/src
        • mkdir -p mydir; cd mydir
      • install python3 in a virtualenv
        • virtualenv env
        • source env/bin/activate
        • pip install --upgrade pip
      • install django
        • pip install "django<3"
      • create django project:
        • django-admin startproject myproject .
      • create application
        • ./manage.py startapp myapp

      mydir/myproject/urls.py
      • from django.urls import path, include

        urlpatterns = [
            path('myapp/', include('myapp.urls')),
        ]
      mydir/myapp/urls.py
      • from django.urls import path
        from . import views

        urlpatterns = [
            path('<int:question_id>/', views.detail, name='detail'),
        ]


      mydir/myapp/views.py
      • from django.http import HttpResponse
        def detail(request, question_id):
            return HttpResponse("You're looking at question %s." % question_id)

      • http://127.0.0.1:8000/myapp/1/
      • http://127.0.0.1:8000/myapp/2/
      • ...
      view with html template
      mydir/myproject/settings.py (needed to find application templates)
      • INSTALLED_APPS = [
            ...
            'myapp',
        ]
      mydir/myproject/urls.py
      • from django.urls import include, path
        urlpatterns = [
            path('myapp/', include('myapp.urls')),
        ]
      mydir/myapp/urls.py
      • from django.urls import path

        from . import views

        urlpatterns = [
            path('', views.index, name='index'),
        ]


      mydir/myapp/views.py
      • from django.shortcuts import render


        def index(request):
            context = {'day_number': 4}
            return render(request, 'myapp/index.html', context)
      mydir/myapp/templates/myapp/index.html
      • Today is: {{ day_number }}.
      • http://127.0.0.1:8000/myapp/
      post view
      • create dir
        • cd ~src
        • mkdir -p mydir; cd mydir
      • install python3 in a virtualenv
        • virtualenv env
        • source env/bin/activate
        • pip install --upgrade pip
      • install django
        • pip install django
      • create django project:
        • django-admin startproject myproject .
      • create application
        • ./manage.py startapp myapp

      mydir/myproject/urls.py:
      • from django.urls import include, path

        urlpatterns = [
            path('myapp/', include('myapp.urls')),
        ]
      mydir/myapp/urls.py:
      • from django.urls import path

        from . import views

        app_name = 'myapp'
        urlpatterns = [
            path('toto/', views.toto, name='toto'),
        ]


      myproject/mysite/myapp/views.py:
      • from django.http import HttpResponse
        from django.views.decorators.csrf import csrf_exempt

        @csrf_exempt
        def toto(request):
            try:
                primer = request.POST['primer']
            except:
                return HttpResponse("missing POST parameter: primer", content_type="text/plain")
            return HttpResponse("primer: {0}".format(primer), content_type="text/plain")

      • curl -i -X POST http://localhost:8000/myapp/toto/ -F primer=2
      calendar html view
      (events as an array)
      • create dir
        • cd ~/src
        • mkdir -p calendar_dir; cd calendar_dir
      • install python3 in a virtualenv
        • virtualenv env
        • source env/bin/activate
        • pip install --upgrade pip
      • install django
        • pip install "django<3"
      • create django project:
        • django-admin startproject calendar_project .
      • create application
        • ./manage.py startapp events
      • create database
        • ./manage makemigrations
        • ./manage migrate
      calendar_dir/calendar_project/settings.py
      • INSTALLED_APPS = [
            ...
            'events',
        ]
      calendar_dir/calendar_project/urls.py
      • from django.contrib import admin
        from django.urls import include, path

        urlpatterns = [
            path('admin/', admin.site.urls),
            path('events/', include('events.urls')),
        ]
      calendar_dir/events/urls.py
      • from django.urls import path

        from . import views

        urlpatterns = [
            path('', views.index, name='index'),
            path('calendar', views.calendar, name='calendar'),
        ]
      calendar_dir/events/models.py
      • from django.db import models

        class Event(models.Model):
            id = models.AutoField(primary_key=True)
            name = models.CharField(max_length=255,null=True,blank=True)
            start = models.DateTimeField(null=True,blank=True)
            end = models.DateTimeField(null=True,blank=True)

            def __str__(self):
                return self.name
      calendar_dir/events/admin.py
      • from django.contrib import admin

        from .models import Event

        admin.site.register(Event)
      calendar_dir/events/views.py
      • from django.shortcuts import render

        from .models import Event


        def calendar(request):
            all_events = Event.objects.all()
            context = {
                "events":all_events,
            }
            return render(request,'events/calendar.html',context)
      calendar_dir/events/templates/events/calendar.html
      • <html>
        <head>
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.css"/>
            <link rel="stylesheet"
                  href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css"/>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"></script>
            <script>

                $(document).ready(function () {
                    var calendar = $('#calendar').fullCalendar({
                        header: {
                            left: 'prev,next today',
                            center: 'title',
                            right: 'month,agendaWeek,agendaDay'
                        },
                        events: [
                            {% for event in events %}
                                {
                                    title: "{{ event.name }}",
                                    start: '{{ event.start|date:"c" }}',
                                    end: '{{ event.end|date:"c" }}',
                                    id: '{{ event.id }}',
                                },
                            {% endfor %}
                        ],

                        selectable: true,
                        selectHelper: true,
                        editable: true,
                        eventLimit: true,
                    });
                });

            </script>
        </head>
        <body>
          <br/>
          <h2 align="center"><a href="#">title</a></h2>
          <br/>
          <div class="container">
            <div id="calendar"></div>
          </div>
        </body>
        </html>
      • http://127.0.0.1:8000/admin
      • http://127.0.0.1:8000/events/calendar
      calendar html view
      (events as a json feed)



      urls.py
      • from django.urls import path

        from . import views

        urlpatterns = [
            path('calendar', views.calendar, name='calendar'),
            path('event_list', views.event_list, name='event_list'),
        ]


      views.py
      • import json
        from datetime import datetime

        from django.shortcuts import render
        from django.http import HttpResponse

        from .models import Event


        def calendar(request):
            return render(request,'events/calendar.html')

        def event_list(request):
            start = request.GET['start']
            end = request.GET['end']

            // when timezone is set to UTC, requests are of type:
            //
        /event_list?start=2020-01-26T00%3A00%3A00Z&end=2020-01-27T00%3A00%3A00Z&timezone=UTC&_=1580059864316     start_datetime = datetime.strptime(start, '%Y-%m-%dT%H:%M:%SZ')
            end_datetime = datetime.strptime(end, '%Y-%m-%dT%H:%M:%SZ')

            events = Event.objects.filter(start__range=(start_datetime, end_datetime))

            result = []
            for event in events:
                print("- event: {}".format(event.name))
                result.append({
                    'id': event.id,
                    'title': event.name,
                    'start': event.start.isoformat(),
                    'end': event.end.isoformat()
                })
           
            return HttpResponse(json.dumps(result), content_type="application/json")
      calendar.html
      • <html>
        <head>
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.css"/>
            <link rel="stylesheet"
                  href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css"/>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.10.1/fullcalendar.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.10.1/locale/ca.js"></script>
            <script>

                $(document).ready(function () {
                    var calendar = $('#calendar').fullCalendar({
                        header: {
                            left: 'prev,next today',
                            center: 'title',
                            right: 'month,agendaWeek,agendaDay'
                        },
                        timezone: 'UTC',
                        locale: 'ca',
                        events: 'event_list',
                        defaultView: 'agendaDay',
               
                eventTimeFormat: 'H:mm', // uppercase H for 24-hour clock
               
                nowIndicator: true,
               
                //slotLabelInterval: {hours: 2},
               
                slotLabelFormat: 'H:mm',
                        selectable: true,
                        selectHelper: true,
                        editable: true,
                        eventLimit: true,
                    });
                });

            </script>
        </head>
        <body>
          <br/>
          <h2 align="center"><a href="#">title</a></h2>
          <br/>
          <div class="container">
            <div id="calendar"></div>
          </div>
        </body>
        </html>

      library
      • create dir
        • cd ~src
        • mkdir -p library_dir; cd library_dir
      • (install python2 in a virtualenv)
        • virtualenv -p /usr/bin/python2.7 env
      • install python3 in a virtualenv
        • virtualenv env
        • source env/bin/activate
        • pip install --upgrade pip
      • install django
        • pip install "django<3"
      • create django project:
        • django-admin startproject library_project .
      • create application
        • ./manage.py startapp library_app
      library_dir/library_project/settings.py
      • INSTALLED_APPS = [
            ...
            'library_app',
        ]
      library_dir/library_project/urls.py
      • ...
      library_dir/library_app/urls.py:
      • ...
      library_dir/library_app/models.py
      • ...
      library_dir/library_app/admin.py
      • ...
      library_dir/libray_app/views.py
      • ...

      • http://127.0.0.1:8000/admin/
      • http://127.0.0.1:8000/library_app/

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(self, request, *args, **kwargs)
  • get(self, request, *args, **kwargs)
  • get(self, request, *args, **kwargs)
  • put(self, request, *args, **kwargs)
  • patch(self, request, *args, **kwargs)
  • delete(self, request, *args, **kwargs)
Renderers:
  • JSONRenderer
  • UnicodeJSONRenderer
  • JSONPRenderer
  • YAMLRenderer
  • UnicodeYAMLRenderer
  • XMLRenderer
  • TemplateHTMLRenderer (Django)
  • StaticHTMLRenderer
  • HTMLFormRenderer
  • BrowsableAPIRenderer
  • MultiPartRenderer
  • Custom renderers
(explicitly defined at derived class)
  • POST
  • GET
  • PUT
  • PATCH
  • DELETE
url(... MyAPIView.as_view() ),
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
      • Deprecation
      • Index
      • 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)
      • FileField
      • 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 )
      • UUIDField
      • 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
        • How to Use Date Picker with Django
        • 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 )
      • JSONField
        • introduced in Django 1.9
        • it can be used with PostgreSQL >= 9.4
        • default empty list
        • Trying JSON in Django and PostgreSQL (and compare with MongoDB)
        • Problems with json reencoding
        • admin
          • JSONEditorWidget
            • pip install https://github.com/jmrivas86/django-json-widget/archive/master.zip
            • from django.contrib.postgres.fields import JSONField
              from django_json_widget.widgets import JSONEditorWidget

              class MyClassAdmin(admin.ModelAdmin):
                  # list
                  ...
                  # detail
                  ...

                  formfield_overrides = {
                      JSONField: {'widget': JSONEditorWidget}
                  }
            •     formfield_overrides = {
                      JSONField: {'widget': JSONEditorWidget(height="300px")}
                  }
          • PrettyJSONWidget
            • from django.contrib.postgres.fields import JSONField
              from django.forms import widgets

              class PrettyJSONWidget(widgets.Textarea):

                  def format_value(self, value):
                      try:
                          value = json.dumps(json.loads(value), indent=2, sort_keys=True)
                          # these lines will try to adjust size of TextArea to fit to content
                          row_lengths = [len(r) for r in value.split('\n')]
                          self.attrs['rows'] = min(max(len(row_lengths) + 2, 10), 30)
                          self.attrs['cols'] = min(max(max(row_lengths) + 2, 40), 120)
                          return value
                      except Exception as e:
                          return super(PrettyJSONWidget, self).format_value(value)

              class MyClassAdmin(admin.ModelAdmin):
                  # list
                  ...
                  # detail
                  ...

                  formfield_overrides = {
                      JSONField: {'widget': PrettyJSONWidget}
                  }
      • PostgreSQL specific model fields
        • ArrayField
          • example
            • from django.contrib.postgres.fields import ArrayField

              my_array_field = ArrayField(...)
          • default empty list
            • my_arrayfield = ArrayField(... default=list, blank=True)
      • Relations
        one to one
        foreign key generic foreign key many to many
        • one ... can have only one ...
        • one manufacturer (only Manufacturer objects) can have several cars
        • one bookmark or one note can have several tags
        • several ... can have several ...




        direct inverse through

        class Manufacturer(models.Model):
            """
            A manufacturer consists of usual fields, and 0 or more cars
            """
            # usual fields
        class Bookmark(models.Model):
            """
            A bookmark consists of a URL, and 0 or more descriptive tags.
            """
            # usual fields
            url = models.URLField()

            # generic relation
            tags = GenericRelation(TaggedItem)
        class Note(models.Model):
            """
            A note consists of some text, and 0 or more descriptive tags.
            """
            # usual fields
            text = models.CharField(max_length=1000)

            # generic relation
            tags = GenericRelation(TaggedItem)




        class Car(models.Model):
            # usual fields

            # foreign key
            manufacturer = models.ForeignKey(
                Manufacturer,
                # van be 'Manufacturer' (name) if not defined yet
                on_delete=models.CASCADE,
                related_name = "cars",
            )
        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')



      • ForeignKey
        • one model can be attached to only one kind of model
        • class Toto(models.Model):
              user = models.ForeignKey(MyUser, verbose_name=_("User"), help_text=_("This is an extended help"), related_name='myuser')
        • Admin ForeignKey
        • related_name
        • on_delete
          • What does adding on_delete to models.py do, and what should I put in it? [duplicate]
          • mandatory from Django 2
          • when parent instance is deleted, also delete child instance (was default on Django<2):
            • class ChildModel(models.Model):
                  parent = models.ForeignKey(ParentModel, on_delete=models.CASCADE)
          • when parent instance is deleted, do not delete child instance:
            • class ChildModel(models.Model):
                  parent = models.ForeignKey(ParentModel, on_delete=models.SET_NULL, null=True)
      • GenericForeignKey
        • one model can be attached to several kind of models
        • Admin GenericForeignKey
        • Avoid Django's GenericForeignKey
          • non trivial filtering
        • Generic relations (The contenttypes framework)
        • How to Use Django's Generic Relations
        • How to use GenericForeignKey in Django
        • on_delete
          • Reverse generic relations
            • "Unlike ForeignKey, GenericForeignKey does not accept an on_delete argument to customize this behavior; if desired, you can avoid the cascade-deletion by not using GenericRelation, and alternate behavior can be provided via the pre_delete signal."
        • 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 (see also table above):
          • 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()
                # IMPORTANT: adding GenericRelations forces on_delete=models.CASCADE:
                #  when a Bookmark object is deleted, all tags are also deleted
                # If this GenericRelation was not specified, when a Bookmark object was deleted, all related tags
                # would not be deleted and would remain pointing to a non existing object (defined by a pair of content_type/object_id)
                tags = GenericRelation(TaggedItem, related_query_name="bookmarks")
                # implies default names for fields:

                # tags = GenericRelation(TaggedItem, related_query_name="bookmarks", content_type_field="content_type", object_id_field="object_id")

            class Note(models.Model):
                """
                A note consists of some text, and 0 or more descriptive tags.
                """
                text = models.CharField(max_length=1000)
               
                # IMPORTANT: adding GenericRelations forces on_delete=models.CASCADE:
                #  when a Note object is deleted, all tags are also deleted
               
                tags = GenericRelation(TaggedItem
            , related_query_name="notes")
          • my_bookmark = Bookmark.objects.create(...)
            my_first_bookmark_tag = TaggedItem.objects.create(content_object=my_bookmark, ...)
            bookmark_from_tag = my_first_bookmark_tag.bookmarks.first() my_bookmark_tags = my_bookmark.tags.all()
          • my_note = Note.objects.create(...) my_first_note_tag = TaggedItem.objects.create(content_object=my_note, ...)
            note_from_tag =
            my_first_note_tag.notes.first() my_note_tags = my_bookmark.tags.all()
        • Problemes / Problems
      • 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
      • Encrypted fields
      • ...
    • 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
    • Excepcions / Exceptions
      • IntegrityError
        • Problem with atomic transactions
          • To avoid message "An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.":
            • protect with transaction.atomic() (Controlling transactions explicitly):
              • from django.db import IntegrityError, transaction

                @transaction.atomic
                def viewfunc(request):
                    create_parent()

                    try:
                        with transaction.atomic():
                            generate_relationships()
                    except IntegrityError:
                        handle_exception()

                    add_children()
        • ...
    • Grafs / Graphs
      • Django - Model graphic representation (ERD)
      • django-extensions
        • docs
        • instal·lació / installation
          • pip uninstall pyparsing
          • pip install pyparsing==1.5.7
          • pip install pydot
          • pip install "django-extensions<3" "Django<2"
          • settings.py
            • INSTALLED_APPS = (
                  ...
                  'django_extensions',
                   ...
              )
        • Ús / Usage
          • runscript
          • show_urls
            • show all endpoints and reverse
          • ...
        • Graph models
          • instal·lació / installation
            • Mageia
              • urpmi graphviz
          • utilització / usage
            • python manage.py graph_models my_app | dot -Tpdf > my_app.pdf
      • django-graphviz
    • 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
        • ..
  • Escriptura i lectura a/des de la base de dades / Writing and reading to/from database
    • Escriptura: un model s'escriu a la base de dades quan passa per django/db/transaction.py: __exit__().connection_commit()
    • des de l'admin això no es fa fins que es retorna la petició; hi pot haver comportaments estranys si mentre encara no s'ha desat l'objecte es fa una petició a la base de dades (per exemple una petició a un endpoint disparada dins del save o del post_save)
  • 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()
        • union()
          • Problemes / Problems
            • ORDER BY term does not match any column in the result set
              • Solució / Solution
                • dynamically build Q expression (if it applies):
                  • from django.db.models import Q

                    q_expression = Q(...)
                    for ...:
                        q_element = Q(...)
                        q_expression |= q_element
                    qs = MyModel.objects.filter(q_expression)
        • Exemples / Examples
        • .filter()
          • Aggregation (Aggregation functions)
            • 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
              • filtrar pel nombre d'objectes relacionats / filter by number of related objects
                • from django.db.models import Count

                  MyModel.objects.annotate(num_items=Count("relitems")).filter(num_items__gte=1)
          • 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
      • get a list of all endpoints and all reverse values:
      • reverse als test unitaris
      • 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




          python
          template





          context = {}
          # opts may be used by {% url opts|...
          context["opts"] = self.model._meta # needed by admin_urlname in template
          AdminSite
          Index
          index

          reverse("admin:index") <a href="{% url 'admin:index' %}">Home</a>
          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 (see ModelAdmin.changelist_view)
          • 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
          original (is the instance)
          ... (see ModelAdmin.render_change_form)
          • from django.utils.safestring import mark_safe

            url = reverse("admin:myapp_mymodel_change", args=[my_object_id])
            self.message_user(
                request,
                mark_safe("Successfully created instance in other model: <a href=\"{}\">new instance</a>".format(url))
            )
          {% url 'admin:myapp_mymodel_change' mymodel.id %}
          (custom) {{ app_label }}_{{ model_name }}_myview

          myview must be registered at MyModelAdmin.get_urls()
          • {% url 'admin:myapp_mymodel_myview' %}
          • {% url opts|admin_urlname:"myview" %}
  • 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 response
        type
        return ...
        import

        "toto"


        HttpResponse("value: %s" % toto) from django.http import HttpResponse
        text/plain
        HttpResponse("toto text", content_type="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
        # deprecated (use render() instead)
        render_to_response('polls/detail.html', {'poll_var': p})
        from django.shortcuts import render_to_response
        # deprecated (use render() instead)
        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)
    • Trailing slash
    • Redirect
    • 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:
          • 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 from:
          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 or Nginx:
      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
  • Resum / Summary
    type
    storage
    destination
    access



    root path <MEDIA_URL>/...
    local

    <MEDIA_ROOT>/<upload_to>/<filename>



    • default (settings.py):
    • non-default (models.py):
      • my_field = models.FileField( ... storage=OverwriteStorage() ...)
    • default (settings.py):
    • non-default (.py):
      • ...
    • static (models.py):
      • media = models.ImageField(upload_to='im')
    • runtime (models.py):
      • def media_filename(instance, filename):
        ...
      • media = models.ImageField(upload_to=media_filename)
    remote AWS S3
    s3://<AWS_STORAGE_BUCKET_NAME>/<location>/<upload_to>/<filename>



    • default:
      • settings.py
        • DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3Boto3Storage'
        • DEFAULT_FILE_STORAGE = 'myapp.s3utils.MyFirstBucketS3Storage'
      • models.py
        • class MyModel(models.Model):
              # uses default storage, specified by DEFAULT_FILE_STORAGE
              my_field = models.FileField()
    • non-default:
      • models.py
        • from s3utils import MyFirstS3Storage, MySecondS3Storage

          class MyModel(models.Model):
              my_field = models.FileField(storage=MyFirstS3Storage(), upload_to=...)
    • default (settings.py):
      • bucket:
        • AWS_STORAGE_BUCKET_NAME = 'mybucket'
      • root path:
        • AWS_LOCATION = "mylocation"
        • DEFAULT_S3_PATH = "media"
          MEDIA_ROOT = '/%s/' % DEFAULT_S3_PATH
    • non-default:
      • bucket:
        • s3utils.py
          • @deconstructible
            class MyFirstBucketS3Storage(S3Boto3Storage):
                bucket_name = 'myfirstbucket'

          • @deconstructible
            class MyFirstBucketS3Storage(S3Boto3Storage):
                def __init__(self, *args, **kwargs):
                    kwargs['bucket'] = 'myfirstbucket'
                    kwargs['file_overwrite'] = False

                    super(MyFirstBucketS3Storage, self).__init__(*args, **kwargs)
      • location
        • s3utils.py
          • @deconstructible
            class NewMediaS3BotoStorage(S3Boto3Storage):
                location='mylocation'
    (same as above)
    • my_field.url
    • default (settings.py):
      • DEFAULT_S3_PATH = "media"
        CLOUDFRONT_DOMAIN = 'domain.cloudfront.net'
        MEDIA_URL = 'http://%s/%s/' % (CLOUDFRONT_DOMAIN, DEFAULT_S3_PATH)
    • non default:
      • s3utils.py
        • @deconstructible
          class MyFirstBucketS3Storage(S3Boto3Storage):
              # to be used with CloudFront
              custom_domain = 'myfirstbucket.mydomain.com'

        • @deconstructible
          class MyFirstBucketS3Storage(S3Boto3Storage):
              def __init__(self, *args, **kwargs):
                  kwargs['custom_domain'] = 'myfirstbucket.mydomain.com'

                  super(MyFirstBucketS3Storage, self).__init__(*args, **kwargs)
  • 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
    • Progrés / Progress
    • 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
  • ...

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':
                        # NOTE: use allow_null=True instead
                        # 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
    • fields from related fields
      • admin.py
        • class MyModelAdmin(admin.ModelAdmin):
              fields = ('other_field',)
              readonly_fields = ('other_field',)

              def other_field(self, obj):
                  return obj.related_field.other_field
    • 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
                    # python 2: if kwargs.has_key('instance'):
                    if "instance" in kwargs:
                        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

Eclipse

  • Estructura de directoris
  • 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
    • 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 -> Add variable
        • DJANGO_MANAGE_LOCATION: manage.py (this will allow project to be run/debug as Django)
        • DJANGO_SETTINGS_MODULE: my_project.settings
  • Debug / Run
    • first time
      • select your project
        • Debug As -> PyDev: Django
    • next times
      • from top menu, select your entry from dropdown over debug or run icon
  • Test
    • General method
      • First time: select your project
        • Django -> Run Django Tests
      • Next time: you can select from menu (but this only works if the first time run ok; if not, follow the alternative method)
    • Alternative method: copy debug configuration from main debug
      • select your project
        • Debug As: PyDev Django
      • duplicate the Debug configuration to a new one and modify it
    • Alternative method: custom command
      • select your project
        • Django -> Custom command
          • e.g.: test --settings my_project.test_settings --verbosity 3 ...
      • modify Debug configuration:
        • Main
          • Project: myproject
          • Main Module: ${workspace_loc:wct_streaming_farm}/${DJANGO_MANAGE_LOCATION}
        • Arguments
          • Program arguments:
            • test --settings my_project.tests_settings --verbosity 3 ...
          • Working directory
            • Default: ${project_loc:/selected project name}
        • ...
  • Breakpoints
    • debug response, even before reaching your code:
      • .../lib/python2.7/site-packages/django/core/handlers/wsgi.py: line 168 (WSGIHandler.__call__: return response)
      • when performing tests
        • .../lib/python2.7/site-packages/django/test/client.py: ClientHandler.__call__: response = self.get_response(request) (line 144)
  • Errors
    • Undefined variable from import
      • ...
  • 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)
  • Creació / Creation

    • first host (e.g. local server) second host (e.g. moving from local server to AWS Aurora)

      hostname=127.0.0.1
      su postgres
      hostname=...rds.amazonaws.com
      create user (using master_user)
      and give it permissions to create databases
      master_user=postgres
      db_user=my_user
      db_password=my_password
      psql --host=${hostname} --username=${master_user} --password --command="CREATE USER ${db_user} WITH PASSWORD \'${db_password}\' CREATEDB;"
      create database (using db_user) db_name=my_db
      createdb --host=${hostname} --password --username=${db_user} ${db_name}
      • only needed if not using pg_restore --create
      setup GIS (optional) in created database psql --host=${hostname} --password --username=${db_user} --dbname=${db_name} --command="CREATE EXTENSION postgis;" (not needed; it will be done by my_db_dump.sql)
      dump single database content dump_filename=my_db_dump.sql pg_dump --dbname=${db_name} --file=${dump_filename}
      get content from single database dump
      • # this does not accept --create nor --format=directory
        psql --host=${hostname} --password --username=${master_user}
        --dbname=${db_name} --file=${dump_filename}
      • # not working?: pg_restore --host=${hostname} --password --username=${master_user} --create --file=${dump_filename}

      # for small databases
      pg_dump --dbname=${db_name} | pgsql --host=${hostname} --password --username=${master_user} --dbname=${db_name}
  • Clear database
  • Bolcat / Dump
  • 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'

  • 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.
              }
          }
  • Multiple databases
    • Replicació / Replication
    • Example 1: default for read/write, readonly for read
      • Scaling Django with Postgres Read Replicas
      • When and How to Configure a Read Replica Database in Django
      • my_project/settings.py 
        • DATABASES = {
              # e.g.: AWS Aurora Cluster endpoint
              'default': {
                  'ENGINE': 'django.db.backends.postgresql',
                  'NAME': 'my_db_master',
                  'USER': 'my_master_user',
                  'PASSWORD': 'my_master_password',
                  'HOST': 'host_for_default',
                  'PORT': '', # Set to empty string for default.
              },
              # e.g.: AWS Aurora Reader endpoint
              'replica_1': {
                  'ENGINE': 'django.db.backends.postgresql',
                  'NAME': 'my_db_replica_1',
                  'USER': 'my_replica_1_user',
                  'PASSWORD': 'my_replica_1_password',
                  'HOST': 'host_for_replica_1',
                  'PORT': '', # Set to empty string for default.
              },
              # e.g.: AWS Aurora Reader endpoint
              'replica_2': {
                  'ENGINE': 'django.db.backends.postgresql',
                  'NAME': 'my_db_replica_2',
                  'USER': 'my_replica_2_user',
                  'PASSWORD': 'my_replica_2_password',
                  'HOST': 'host_for_replica_1',
                  'PORT': '', # Set to empty string for default.
              }
          }

          DATABASE_ROUTERS = ['my_project.routers.MyRouter']
      • my_project/routers.py 
        • import random

          class MyRouter:
              #route_app_labels = {}

              def db_for_read(self, model, **hints):
                  return random.choice(['replica_1', 'replica_2'])

              def db_for_write(self, model, **hints):
                  return "default"

              def allow_relation(self, obj1, obj2, **hints):
                  return True

              def allow_migrate(self, db, app_label, model_name=None, **hints):
                  return True
    • Problemes / Problems
    • Unit test
      • Option 1: when testing replicas
        • Tests and multiple databases
        • NOT WORKING:
          • Running Tests When Replication Is Active
            • "You are supposed to be able to mark a database as a “test mirror”, but this appears not to work."
            • Workaround:
              • my_project/test_settings.py
                • # Extends the regular settings of the project.
                  from .settings import *

                  # disable readonly replicas, as they are not working in tests
                  # https://andrewbrookins.com/python/scaling-django-with-postgres-read-replicas/#running-tests-when-replication-is-active
                  DATABASE_ROUTERS = []
              • python manage test --settings my_project.tests_settings
          • TEST_MIRROR setting doesn't work as expected (and has no tests)
        • settings.py
          • DATABASES = {
                'replica_1': {
                    ...
                    'TEST': {
                        'MIRROR': 'default',
                    }
                }
            }
      • Option 2: force to use only one database (e.g. 'default'):
    • ...
    • AWS Aurora
    • Debug
      • get instance:
        • User.objects.all()
        • User.objects.using('replica_1').all()
        • User.objects.using('replica_2').all()
        • ...
      • save instance
        • my_user.save()
        • my_user.save(using='default')
        • my_user.save(using='replica_1')
      • given an instance, get the database it is stored in:
        • myobj._state.db
      • ...
  • 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
      show all migrations
      showmigrations
      squash migrations
      squashmigrations
      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 
    • Info
    • migrate
      • Problemes / Problems
        • ./manage.py migrate my_app xxxx
          psycopg2.errors.UndefinedColumn: column my_app_my_model.my_field does not exist
          • when creating a database from scratch
          • my_field is not explicitly referenced in xxx migration, but it was created in a more recent migration yyyy > xxxx
          • but migration xxxx file has "from my_app.models import my_model", and the code already has a reference to my_field in class my_model
          • Solutions
            • temporary solution:
              • migrate my_app xxxx --fake
            • definitive solution: if you do not need RunPython any more, you can comment it on xxx...py file:
              • operations = [
                    # migrations.RunPython(...)
                ]
    • Squash migrations
      • steps:
        1. squash migrations, keeping old files
          • ./manage.py migrate
          • ./manage.py showmigrations myapp1
          • ./manage.py squashmigrations myapp1 xxxx
            • will create: myapp1/migrations/0001_squashed_xxxx_auto_yyyymmdd_hhmm.py
          • ./manage.py migrate myapp
            • you may need to run: ./manage.py makemigrations myapp if you are asked to (e.g. for models.DateTimeField(auto_now_add=True))
          • ./manage.py showmigrations myapp2
          • ...
        2. commit and release
        3. deploy changes to all your environments
          • deploy files
          • on remote (only if some migration files where created after the squash):
            • ./manage migrate
        4. transition the squashed migration to a normal migration:
          • delete all files it replaces (referenced in replaces)
          • remove "replaces = [...]" attribute in the Migration class of the squashed migration (myapp1/migrations/0001_squashed_xxxx_auto_yyyymmdd_hhmm.py)
          • check "dependencies = [...]": you must replace entries pointing to just removed files (usually migration files on another application) by an entry pointing to their replacement (usually the squashed migration on the other application)
        5. commit and release
        6. deploy changes to all your environments
          • deploy files
          • no need to run ./manage.py migrate
      • Problemes / Problems
        • the generated file gives a syntax error
        • RunPython
          • Solució / Solution
            • if you really don't need a PythonRun function (e.g. from a set of manual migrations to include a uuid field), mark it as elidable (from the origin migration file):
              • class Migration(migrations.Migration):
                    ...
                    operations = [
                        migrations.RunPython(myfunction, reverse_code=migrations.RunPython.noop, elidable=True),
                    ]
        • CircularDependencyError
        • ...
    • 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
        • revert to status previous to initial:
          • python manage.py migrate zero
      • Restart from scratch
        • django-reset-migrations
          • for each application, reset its migrations:
            • ./manage.py reset_migrations my_app_1
            • if you get an error: django.db.migrations.exceptions.NodeNotFoundError: Migration my_other_app_related_to_1 ...
              • ./manage.py reset_migrations my_other_app_related_to_1
            • ./manage.py reset_migrations my_app_2
            • if you get an error: django.db.migrations.exceptions.NodeNotFoundError: Migration my_other_app_related_to_2 ...
              • ./manage.py reset_migrations my_other_app_relatec_to_2
            • ...
          • for each application where you got an error, regenerate its migrations:
            • ./manage.py makemigrations my_app_1
            • ./manage.py makemigrations my_app_2
            • ...
          • fake migrate to initial:
            • ./manage.py migrate --fake-initial
        • How to Reset Migrations
          • scenario 1: remove database
            1. remove and create database (PostgreSQL)
              1. sudo su - postgres
              2. database_name="..."
              3. dropdb ${database_name}
              4. createdb ${database_name}
            2. migrate
              1. ./manage.py migrate
            3. create admin user
              1. ./manage createsuperuser
          • scenario 2: keep database
            1. update database:
              • ./manage.py makemigrations
              • ./manage.py migrate
            2. clear migration for each app:
              • applications=$(./manage.py showmigrations 2>/dev/null | awk '/^\S/')
                for app in ${applications}; do ./manage.py migrate --fake ${app} zero; done
              • ./manage.py showmigrations
            3. remove migration files (but protect those in your virtualenv path, e.g. under dir "env"):
              • find . -not -path "./env*" -path "*/migrations/*.py" -not -name "__init__.py" -delete
              • find . -not -path "./env*" -path "*/migrations/*.pyc"  -delete
            4. create the initial migration:
              • ./manage.py makemigrations
            5. fake the initial migration:
              • ./manage.py migrate --fake-initial
              • ./manage.py showmigrations
        • 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

Desplegament servidor HTTP / HTTP server deployment

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

        • level
          mechanism
          Django
          server 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
          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 django.views.decorators.cache import cache_page
              @cache_page(60*2)
              def my_view(request, ...):


            • from rest_framework_extensions.cache.decorators import cache_response
              class MyModelViewSet(viewsets.ModelViewSet):
                  @cache_response(
              settings.CACHE_TIMEOUT_MYMODEL_LISTVIEW, key_func=MyKeyConstructor())
                  def list(self, request, *args, **kwargs):
                      return viewsets.ModelViewSet.list(self, request, *args, **kwargs)

                  @cache_response(settings.CACHE_TIMEOUT_MYMODEL_MYDETAILVIEW, key_func=MyKeyConstructor())
                  @action(...)
                  def my_custom_view(self, request, *args, **kwargs)
                  ...
            • NOTE: @cache_response does not work with @api_view. Apply it to a method (e.g. get()) in views.APIView instead
              • from rest_framework_extensions.cache.decorators import cache_response
                class MyCustomAPIView(views.APIView):
                    @cache_response(settings.CACHE_TIMEOUT_MYCUSTOM_APIVIEW, key_func=MyKeyConstructor())
                    def get(self, request, *args, **kwargs):
                    ...
          cache only pieces

          client downstream
          • browser-based cache
          • squid
          HTTP cache headers
          • Cache-Control
          • 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
  • Timeout

    AWS load balancer nginx uwsgi AWS S3 storage
    config Description
    • Idle timeout: 4000s
    nginx.conf
    • location  / {
        ...
        uwsgi_read_timeout 4000s;
      }
    /etc/uwsgi/vassals/myvassal.ini
    • [uwsgi]
      ...
      # Set internal sockets timeout in seconds.
      # to avoid error "timeout during read(65536) on wsgi.input"
      socket-timeout = 300
    s3utils.py
    • from storages.backends.s3boto3 import S3Boto3Storage
      from django.utils.deconstruct import deconstructible
      from botocore.config import Config

      @deconstructible
      class MyS3Storage(S3Boto3Storage):
          # Possible solution to 504s on long-timed upload requests proposed by:
          #   https://github.com/jschneier/django-storages/issues/279#issuecomment-522063957
          config = Config(
              connect_timeout=4000,
              read_timeout=4000,
              retries={
                  'max_attempts': 20
              }
          )
    raised error when
    timeout is reached
    AWS load balancer logs:
    • 5...

    django logs: 504 Gateway Timeout





  • 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

  • Resum de desplegament de diversos projectes en un únic servidor

    • service
      common projects
      nginx
      /usr/lib/systemd/system/nginx.service
      • [Unit]
        Description=The nginx HTTP and reverse proxy server
        After=network.target remote-fs.target nss-lookup.target

        [Service]
        Type=forking
        PIDFile=/run/nginx.pid
        # Nginx will fail to start if /run/nginx.pid already exists but has the wrong
        # SELinux context. This might happen when running `nginx -t` from the cmdline.
        # https://bugzilla.redhat.com/show_bug.cgi?id=1268621
        ExecStartPre=/usr/bin/rm -f /run/nginx.pid
        ExecStartPre=/usr/sbin/nginx -t
        ExecStart=/usr/sbin/nginx
        ExecReload=/bin/kill -s HUP $MAINPID
        KillSignal=SIGQUIT
        TimeoutStopSec=5
        KillMode=process
        PrivateTmp=true

        [Install]
        WantedBy=multi-user.target
      /etc/nginx/nginx.conf
      • user  nginx;
        worker_processes  1;
        error_log  /var/log/nginx/error.log;
        pid        /run/nginx.pid;

        events {
            worker_connections  1024;
        }

        http {
            include       /etc/nginx/mime.types;
            default_type  application/octet-stream;

            log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                              '$status $body_bytes_sent "$http_referer" '
                              '"$http_user_agent" "$http_x_forwarded_for"';

            access_log  /var/log/nginx/access.log  main;

            sendfile        on;
            #tcp_nopush     on;

            # The Case of the Mysterious AWS ELB 504 Errors
            # https://sigopt.com/blog/the-case-of-the-mysterious-aws-elb-504-errors/
            client_header_timeout = 10s;

            #keepalive_timeout  0;
            keepalive_timeout  65;

            #gzip  on;

            index   index.html index.htm;

            # Load modular configuration files from the /etc/nginx/conf.d directory.
            include /etc/nginx/conf.d/*.conf
        }
      /etc/nginx/conf.d/first-project.conf
      • # the upstream component nginx needs to connect to
        upstream first-project {
            server unix:///var/lib/uwsgi/first-project.sock; # for a file socket
        }

        # configuration of the server
        server {
            # the port your site will be served on
            listen      80;

            # the domain name it will serve for
            server_name first-project.example.org;

            charset     utf-8;

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

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

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

            # letsencrypt
            location /.well-known {
                alias /home/myuser/.well-known;
            }

            # Finally, send all non-media requests to the Django server.
            location / {
                uwsgi_pass first-project;
                # to avoid timeout when requested by backend
                uwsgi_read_timeout 180s;
                #include /home/username/src/uwsgi-tutorial/mysite/uwsgi_params; # the uwsgi_params file you installed
                include /etc/uwsgi/uwsgi_params;
            }
        }
      /etc/nginx/conf.d/second-project.conf
      • ...
      uwsgi
      /usr/lib/systemd/system/emperor.uwsgi.service
      • [Unit]
        Description=uWSGI Emperor
        After=syslog.target

        [Service]
        # yum install uwsgi uwsgi-plugin-python2 ExecStart=/usr/sbin/uwsgi --ini /etc/uwsgi/emperor.ini
        Restart=always
        KillSignal=SIGQUIT
        Type=notify
        StandardError=syslog
        NotifyAccess=all

        [Install]
        WantedBy=multi-user.target
      /etc/uwsgi/emperor.ini
      • [uwsgi]
        emperor = /etc/uwsgi/vassals
        uid = django
        gid = django
      /etc/uwsgi/vassals/first-project.ini
      • [uwsgi]
        chdir = /path/to/first-project
        module = first_project.wsgi
        home = /path/to/first-project/env
        master = true
        processes = 10
        socket = /var/lib/uwsgi/first-project.sock
        chmod-socket = 666
        vacuum = true
        plugin = python
      /etc/uwsgi/vassals/second-project.ini
      • ...
      celery
      /usr/lib/systemd/system/celery.service
      • [Unit]
        Description=Celery workers
        After=network.target rabbitmq-server.service

        [Service]
        Type=forking
        # User and Group cannot be specified at EnvironmentFile
        User=django
        Group=django
        EnvironmentFile=-/etc/sysconfig/celery

        # run ExecStartPre as priviledged user and set up /var/run
        PermissionsStartOnly=true
        #ExecStartPre=/usr/sbin/useradd celery
        ExecStartPre=-/usr/bin/mkdir -p ${CELERYD_PID_DIR}
        ExecStartPre=/usr/bin/chown -R django:django ${CELERYD_PID_DIR}
        ExecStartPre=-/usr/bin/mkdir -p ${CELERYD_LOG_DIR}
        ExecStartPre=/usr/bin/chown -R django:django ${CELERYD_LOG_DIR}

        # same value as ${CELERYD_PID_FILE}, but must be specified.
        # needed to avoid some immediate stops of the service
        PIDFile=/var/run/celery/w1.pid

        # executable cannot be specified at EnvironmentFile
        ExecStart=/path/to/first-project/env/bin/python -m celery multi start ${CELERYD_NODES} \
            -A ${CELERY_APP} \
            --workdir=${CELERYD_CHDIR} \
            --pidfile=${CELERYD_PID_FILE} \
            --logfile=${CELERYD_LOG_FILE} \
            --loglevel="${CELERYD_LOG_LEVEL}" \
            ${CELERYD_OPTS}
        ExecStop=
        /path/to/first-project/env/bin/python -m celery multi stopwait ${CELERYD_NODES} \
            --pidfile=${CELERYD_PID_FILE}
        ExecReload=
        /path/to/first-project/env/bin/python -m celery multi restart ${CELERYD_NODES} \
            -A ${CELERY_APP} \
            --workdir=${CELERYD_CHDIR} \
            --pidfile=${CELERYD_PID_FILE} \
            --logfile=${CELERYD_LOG_FILE} \
            --loglevel="${CELERYD_LOG_LEVEL}" \
            ${CELERYD_OPTS}

        [Install]
        WantedBy=multi-user.target
      /usr/lib/systemd/system/celerybeat.service
      • [Unit]
        Description=Celery beat scheduler
        After=network.target

        [Service]
        Type=simple
        User=django
        Group=django
        EnvironmentFile=-/etc/sysconfig/celery
        #WorkingDirectory=$CELERYD_CHDIR

        # run ExecStartPre as priviledged user and set up /var/run
        PermissionsStartOnly=true
        #ExecStartPre=/usr/sbin/useradd django
        ExecStartPre=-/usr/bin/mkdir -p ${CELERYBEAT_PID_DIR}
        ExecStartPre=/usr/bin/chown -R django:django ${CELERYBEAT_PID_DIR}
        ExecStartPre=-/usr/bin/mkdir -p ${CELERYBEAT_LOG_DIR}
        ExecStartPre=/usr/bin/chown -R django:django ${CELERYBEAT_LOG_DIR}
        # user django has to have access to /path/to/first-project/env/bin/python
        #ExecStartPre=/usr/bin/chmod ag+xr -R /home/wct/

        ExecStart=/path/to/first-project/env/bin/python -m celery beat \
            -A ${CELERY_APP} \
            --workdir=${CELERYBEAT_CHDIR} \
            --pidfile=${CELERYBEAT_PID_FILE} \
            --logfile=${CELERYBEAT_LOG_FILE} \
            --loglevel=${CELERYBEAT_LOG_LEVEL} \
            --schedule=${CELERYBEAT_SCHEDULE}
        ExecStop=/bin/systemctl kill celerybeat.service

        [Install]
        WantedBy=multi-user.target
      /etc/sysconfig/celery
      • # this file must be installed as /etc/sysconfig/celery

        ## common worker and beat settings

        # Absolute or relative path to the 'celery' command:
        #CELERY_BIN="/home/centos/my_project/env/bin/python -m celery"

        # App instance to use
        # comment out this line if you don't use an app
        # directory name where celery.py resides
        CELERY_APP="first-project"

        # Workers should run as an unprivileged user.
        #   You need to create this user manually (or you can choose
        #   a user/group combination that already exists, e.g. nobody).
        #CELERYD_USER="celery"
        #CELERYD_GROUP="celery"
        # not working?
        d /var/run/celery 0755 celery celery -
        d /var/log/celery 0755 celery celery -

        ## Worker settings
        CELERYD_NODES="w1"
        CELERYD_OPTS="--time-limit=300 --concurrency=8"
        CELERYD_LOG_DIR="/var/log/celery"
        CELERYD_LOG_FILE="/var/log/celery/%N.log"
        CELERYD_LOG_LEVEL="DEBUG"
        CELERYD_PID_DIR="/var/run/celery"
        CELERYD_PID_FILE="/var/run/celery/%N.pid"
        CELERYD_CHDIR=/path/to/first-project/

        ## Beat settings
        CELERYBEAT_LOG_DIR="/var/log/celery"
        CELERYBEAT_LOG_FILE="/var/log/celery/beat.log"
        CELERYBEAT_LOG_LEVEL="DEBUG"
        CELERYBEAT_PID_DIR="/var/run/celery"
        CELERYBEAT_PID_FILE="/var/run/celery/beat.pid"
        CELERYBEAT_SCHEDULE="/var/run/celery/celerybeat-schedule"
        CELERYBEAT_CHDIR=/path/to/first_project/

        DJANGO_SETTINGS_MODULE="first_project.settings"

  • Resum de serveis i configuracions:



    server

    mysite/settings.py
    wsgi.py / asgi.py
    standalone
    service
    nginx
    WSGI

    mysite/mysite/wsgi.py
    • import os
      from django.core.wsgi import get_wsgi_application

      os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
      application = get_wsgi_application()
    uWSGI
    • uwsgi -i /etc/uwsgi/emperor.ini
    • uwsgi --emperor /etc/uwsgi/vassals --uid django --gid django
    • /usr/lib/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
        # when using system-installed uwsgi (yum install uwsgi): ExecStart=/usr/sbin/uwsgi --ini /etc/uwsgi/emperor.ini
        Restart=always
        KillSignal=SIGQUIT
        Type=notify
        StandardError=syslog
        NotifyAccess=all

        [Install]
        WantedBy=multi-user.target
    • /etc/uwsgi/emperor.ini
      • [uwsgi]
        emperor = /etc/uwsgi/vassals
        uid = django
        gid = django
    • /etc/uwsgi/vassals/mysite.ini
      • # mysite_uwsgi.ini file
        [uwsgi]

        # Django-related settings
        # the base directory (full path)
        chdir           = /path/to/mysite
        # Django's wsgi file (mysite/wsgi.py)
        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


        # when using system-installed uwsgi (yum install uwsgi-plugin-python2)
        #plugins = python
        # when using
        system-installed uwsgi (yum install uwsgi-plugin-python36)
        plugins = python36

    • /etc/nginx/conf.d/mysite.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 Timeout"
                
        # if you are using AWS ELB, check also its idle timeout
                uwsgi_read_timeout 180s;
                #include /home/username/src/uwsgi-tutorial/mysite/uwsgi_params; # the uwsgi_params file you installed
                include /etc/uwsgi/uwsgi_params
            }
        }
    Gunicorn

    ASGI
    ASGI_APPLICATION = "mysite_ws.routing.application"
    # optional:
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": [("redis-server-name", 6379)],
            },
        },
    }
    mysite_ws/mysite_ws/asgi.py
    • import os
      import django
      from channels.routing import get_default_application

      os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite_ws.settings")
      django.setup()
      application = get_default_application()
    Daphne
    • daphne -p 9000 myproject.asgi:application
    • daphne -b 0.0.0.0 -p 9000 myproject.asgi:application
    • daphne -e ssl:443:privateKey=key.pem:certKey=crt.pem django_project.asgi:application
    • /usr/lib/systemd/system/asgi-myproject.service
      • [Unit]
        Description=daphne daemon
        After=network.target

        [Service]
        PIDFile=/run/daphne/pid
        User=root
        Group=root
        WorkingDirectory=/path/to/mysite_ws
        ExecStart=/path/to/mysite_ws/env/bin/daphne --bind 0.0.0.0 --port 9000 --verbosity 0 mysite_ws.asgi:application
        ExecReload=/bin/kill -s HUP $MAINPID
        ExecStop=/bin/kill -s TERM $MAINPID
        Restart=on-abort
        PrivateTmp=true

        [Install]
        WantedBy=multi-user.target
    • /etc/nginx/conf.d/mysite.conf
      • upstream channels-backend {
            server localhost:9000;
        }

        # http and websockets served by daphne
        server {
            listen 80;
            server_name localhost;

            location / {
                try_files $uri @proxy_to_app;
            }
           
            location @proxy_to_app {
                proxy_pass http://channels-backend;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";

                proxy_redirect off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Host $server_name;
            }
        }
      • upstream channels-backend {
            server localhost:9000;
        }

        # https served by uwsgi, websockets served by daphne
        server {
            listen 80;
            server_name localhost;

            location /ws/ {
                try_files $uri @proxy_to_app;
            }
           
            location @proxy_to_app {
                proxy_pass http://channels-backend;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";

                proxy_redirect off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Host $server_name;
            }

            location / {

                uwsgi_pass  django_mysite;
                ...
            }
        }

  • 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
      • Permisos d'execució / Execution permissions
      • Instal·lació / Installation
        • des de paquets de la distribució / from distribution packages
          • CentOS/Alma 8
            • activate GhettoForge repo
              • sudo dnf install https://mirror.ghettoforge.org/distributions/gf/gf-release-latest.gf.el8.noarch.rpm
              • sudo dnf config-manager --enable gf-tesing
            • install
              • sudo dnf install uwsgi uwsgi-plugin-python3
          • CentOS 7
            • sudo dnf install uwsgi uwsgi-plugin-python3
      • 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 Timeout"
                     
                     
              # if you are using AWS ELB, check also its idle timeout
                      # The Case of the Mysterious AWS ELB 504 Errors
                      # https://sigopt.com/blog/the-case-of-the-mysterious-aws-elb-504-errors/
                      client_header_timeout = 180s;

                      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 (mysite/wsgi.py)
                  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 502 (from AWS load balancer)
                  • curl -i ...
                    • HTTP/2 502
                      server: awselb/2.0
                      date: Wed, 07 Jun 2023 13:59:46 GMT
                      content-type: text/html
                      content-length: 122
                  • Solució / Solution
                    • verify target group associated to load balancer: at least one instance (target) should be healthy
                    • verify that health checks are received by instance (security groups, nginx started with correct listen port, ...)
                • Error 502 (sometimes, under heavy load)
                  • /var/log/nginx/error.log
                    • [error] 2223#0: *131046 connect() to unix:///var/lib/uwsgi/....sock failed (11: Resource temporarily unavailable) while connecting to upstream
                  • Solució / Solution
                • Error 502
                • 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
      • Problemes / Problems
    • 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

  • django-cors-headers
    • github
    • my_project/settings.py
      • # CORS (django-cors-headers)
        CORS_ORIGIN_ALLOW_ALL = True
        # based on corsheaders/defaults.py
        CORS_ALLOW_HEADERS = (
            "accept",
            "accept-encoding",
            "authorization",
            "content-type",
            "dnt",
            "origin",
            "user-agent",
            "x-csrftoken",
            "x-requested-with",
            "cache-control",
        )

Plantilles / Templates

  • Templates (1.6)
  • The Django template language
    • Elements
      • variable:
        • {{ original.my_field }}
        • {{ 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 %}
      • custom
    • 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
      • # avoid direct import of User (pylint-django error)
        # from django.contrib.auth.models import User
        from django.contrib.auth import get_user_model

        usuari = get_user_model().objects.create_user("usuariprimer", "usuari@toto.org")

      • from django.contrib.auth import get_user_model

        class MyModelSerializer(serializers.ModelSerializer):
            class Meta(object):
                # NOTE: we cannot use model = settings.AUTH_USER_MODEL because an error would be raised:
                # AttributeError: 'str' object has no attribute '_meta'
                model = get_user_model()

      • from django.conf import settings

        @receiver(..., sender=settings.AUTH_USER_MODEL)
      • from django.conf import settings

        class MyModel(models.Model):
            myfiled_1 = models.ForeignKey(settings.AUTH_USER_MODEL, ...)

            myfield_2 = models.OneToOneField(settings.AUTH_USER_MODEL, ...)
      • 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
      • Autenticació de servidors / Server authentication
      • User authentication in Django (1.4)
        • Authentication: verifies that a user is who he claims to be
        • Authorization: determines what an authenticated user is allowed to do


        • Django
          channels
          django-rest-framework authentication


          (default)
          django-allauth

          (default)
          djangorestframework-jwt djangorestframework-simplejwt django-rest-auth / dj-rest-auth


          authentication authentication
          social authentication

          authentication authorization (permissions)
          authentication
          authentication
          registration
          social authentication


          verifies that a user is who he claims to be




          determines what an authenticated user is allowed to do





          pip install


          django-allauth



          djangorestframework-jwt djangorestframework-simplejwt django-rest-auth / dj-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....'




          • # only if i18n is used
            'rest_framework_simplejwt',
          • 'rest_framework'
          • 'rest_framework.authtoken'
          • 'rest_auth'
          • 'dj_rest_auth'
          • ...
          • 'allauth'
          • 'allauth.account'
          • 'rest_auth.registration'
          • 'dj_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': (...)
          • 'rest_framework_simplejwt.authentication.JWTAuthentication',
          '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 / consumers.py




          from channels.auth import login

          class ChatConsumer(AsyncWebsocketConsumer):

              ...

              async def receive(self, text_data):
                  ...
                  # login the user to this session.
                  await login(self.scope, user)
                  # save the session (if the session backend does not access the db you can use `sync_to_async`)
                  await database_sync_to_async(self.scope["session"].save)()

          ---

          from asgiref.sync import async_to_sync
          from channels.auth import login

          class SyncChatConsumer(WebsocketConsumer):

              ...

              def receive(self, text_data):
                  ...
                  async_to_sync(login)(self.scope, user)
                  self.scope["session"].save()

          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 / routing.py

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

          from channels.auth import AuthMiddlewareStack

          application = ProtocolTypeRouter({

              "websocket": AuthMiddlewareStack(
                  URLRouter([
                      url(r"^front(end)/$", consumers.AsyncChatConsumer),
                  ])
              ),

          })


          • 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')
          from rest_framework_simplejwt.views import (
              TokenObtainPairView,
              TokenRefreshView,
              TokenVerifyView,
          )

          urlpatterns = [
              ...
              path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
              path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
              path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
              ...
          ]
          url(r'^rest-auth/', include('rest_auth.urls'))
          url(r'^rest-auth/', include('dj_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'))
          (r'^rest-auth/registration/', include('dj_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
          • token is not stored in database
          • first call to /api/token to get a jwt token
            • token=$(echo $response | jq -r '.access')
          • next calls to your api must contain the jwt token:
          • token is not stored in database
          • first call to rest-auth/login to get a token
          • next calls to your api must contain the token:
          • /user gives information about the current user
          • Update UserProfile
            • USER_DETAILS_SERIALIZER
          • token is stored in database
          • 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 djangorestframework-simplejwt django-rest-auth


          Django

          django-rest-framework authentication
        • Using the Django authentication system
        • Authentication backends
          • AUTHENTICATION_BACKENDS
            • used by:
            • complete example:
              • settings.py
                • AUTHENTICATION_BACKENDS = (
                      # django-axes must be the first one
                      'axes.backends.AxesModelBackend',
                     
                      # default; still 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',

                      # custom
                      'my_app.auth_backends.MyAuthenticationBackend'
                  )
              • my_app/models.py
                • class MyAuthenticationBackend(object):
                      def authenticate(self, request, **credentials):
                          ...
          • Customizing authentication in Django
        • Authentication execution stack
          • 1st stage: check for headers 2nd stage: view

            login view regular views
            REST_FRAMEWORK. DEFAULT_AUTHENTICATION_CLASSES
            • ...
            • 'rest_framework.authentication.SessionAuthentication'
            • 'rest_framework.authentication.BasicAuthentication'
            • 'rest_framework.authentication.TokenAuthentication',
            • 'rest_framework_simplejwt.authentication.JWTAuthentication'
            AUTHENTICATION_BACKENDS
            • # django-axes must be the first one
              'axes.backends.AxesModelBackend',
            • # default; still 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',
            • # custom (e.g. to login using an external service)
              'my_app.auth_backends.MyAuthenticationBackend'

          • login view: POST /rest-auth/login
            • rest_framework.views.APIView.dispatch
              • self.initial
                • self.perform_authentication
                  • rest_framework.request.user
                    • self._authenticate
                      • for authentication in self.authenticators (BasicAuthentication, TokenAuthentication, ...)
                        • ...
                        • TokenAuthentication.authenticate
                  • ...
                • self.check_permissions
                • self.check_throttles
              • response = handler(request, *args, **kwargs)
                • dj_rest_auth.views.LoginView.post()
                  • serializer.is_valid()
                    • rest_auth.serializers.LoginSerializer.authenticate()
                      • django.contrib.auth.authenticate() (for backend: user = _authenticate_with_backend(backend, ...), # list is defined by AUTHENTICATION_BACKENDS)
                        • axes.backends.AxesModelBackend.authenticate()
                        • django.contrib.auth.backends.ModelBackend.authenticate()
                        • allauth.account.auth_backends.AuthenticationBackend.authenticate()
                        • my_app.auth_backends.MyAuthenticationBackend.authenticate()
                        • ...
                  • login()
                    • self.user = self.serializer.validated_data['user']
                    • self.token = create_token() (by default, calls default_create_token(); but it can be overwritten at settings.REST_AUTH_TOKEN_CREATOR)
                      • rest_auth.utils.default_create_token()
                        • token, _ = token_model.objects.get_or_create(user=user)
                    • self.process_login()
                      • django_login()
                        • django.contrib.auth.login()
                          • request.session.cycle_key()
                            • SessionStore.create()
                  • get_response()
                    • ...
            • ...
          • protected view: GET, POST ... -H 'Authorization: Token xxx' /my/endpoint
            • rest_framework.views.APIView.dispatch
              • self.initial
                • ...
              • response = handler(request, ...)
                • ...my view ...
              • ...
            • ...
          • ...
        • Creating superusers
        • Multi-site
        • Protecció addicional / Additional protection
          • Validació de la contrasenya / Password validation
          • Límit del nombre de reintents / Limit login retries
            • django-axes
              • 4.5.4: Python 2.7
                • pip install django-appconf==1.0.3 django-ipware==2.1.0 django-axes==4.5.4
                • docs
                • settings.py
                  • INSTALLED_APPS = [
                        ...
                        # anywhere
                        'axes',
                        ...
                    ]
                  • AUTHENTICATION_BACKENDS = [
                        # AxesBackend should be the first backend in the AUTHENTICATION_BACKENDS list.
                        'axes.backends.AxesBackend',

                        # Django ModelBackend is the default authentication backend.
                        'django.contrib.auth.backends.ModelBackend',
                    ]
                  • # when using AWS ELB (reverse proxies) AXES_META_PRECEDENCE_ORDER = [
                       'HTTP_X_FORWARDED_FOR',
                       'REMOTE_ADDR',
                    ]
                • python.py manage migrate
                • with allauth
                • with django-rest-auth
                  • use version django-rest-auth>=0.9.4
                  • previous versions have the problem:
                  • login variable must be set at serializer (as its equivalent for allauth in forms.py), so as username is not null in access attempts (e.g. from admin)
                    • myproject/settings.py
                      • AXES_USERNAME_FORM_FIELD = 'login'
                      • REST_AUTH_SERIALIZERS = {
                            ...
                            'LOGIN_SERIALIZER': 'myapp.serializers.MyLoginSerializer',
                        }
                    • myapp/serializers.py
                      • from rest_framework import exceptions
                        from rest_auth.serializers import LoginSerializer

                        class MyLoginSerializer(LoginSerializer):
                            # NOTE: this serializer is partially overwritten (and referenced in settings.REST_AUTH_SERIALIZERS['LOGIN_SERIALIZER'])
                            # to add key "login", defined in settings.AXES_USERNAME_FORM_FIELD, so as username in AccessAttempt is filled correctly.
                            # Inspired by https://django-axes.readthedocs.io/en/latest/6_integration.html#integration-with-django-allauth, where
                            # the same mechanism is implemented in forms (equivalent for serializer).
                           
                            def _validate_email(self, email, password):
                                user = None

                                if email and password:
                                    user = self.authenticate(email=email, password=password, login=email)
                                else:
                                    msg = _('Must include "email" and "password".')
                                    raise exceptions.ValidationError(msg)

                                return user

                            def _validate_username(self, username, password):
                                user = None

                                if username and password:
                                    user = self.authenticate(username=username, password=password, login=username)
                                else:
                                    msg = _('Must include "username" and "password".')
                                    raise exceptions.ValidationError(msg)

                                return user

                            def _validate_username_email(self, username, email, password):
                                user = None

                                if email and password:
                                    user = self.authenticate(email=email, password=password, login=email)
                                elif username and password:
                                    user = self.authenticate(username=username, password=password, login=username)
                                else:
                                    msg = _('Must include either "username" or "email" and "password".')
                                    raise exceptions.ValidationError(msg)

                                return user
                • with django restframework
                • ...
              • 5.x: Python 3, Django 2
              • Problemes / Problems
                • django.core.exceptions.ImproperlyConfigured: django-axes does not work properly with LocMemCache as the default cache backend please add e.g. a DummyCache backend for axes and configure it with AXES_CACHE
                  • Solució / Solution
                    • settings.py
                      • CACHES = {
                            'axes': {
                                'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
                            }
                        }

                        AXES_CACHE = 'axes'
                • class AppConf(metaclass=AppConfMetaClass):
                                             ^
                  SyntaxError: invalid syntax

        • 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
      • Sessions
      • Multi-/Two-factor authentication (MFA)
      • External authentication (other authentication sources)
      • 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_*
          • Django-allauth Tutorial
          • 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
            • dj-rest-auth
              • replacement for django-rest-auth
              • Python 3.5
              • Django 2, 3
              • For Python 3 and Django 1.11:
                • pip install "dj-rest-auth=1.1.0"
              • For Python 2.7 and Django 1.11:
                • pip install "dj-rest-auth==0.1.1"
                  • will not work, as utils.py does not implement import_callable
                • pip install -e "git://github.com/iMerica/dj-rest-auth.git@0.1.4#egg=dj-rest-auth"
                  • will not work, because it requires Python>=3.5
            • django-rest-auth
              • IMPORTANT: use dj-rest-auth instead
              • 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 = ("__str__",)

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

            #
            list_display_links = ("field_2",)

            # 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"], "description": "This group is cool"}),
                (_("Group A"), {"fields": ["
        field_3", "field_4"], "classes": ["wide"]}),
                (_("Group B"), {"fields": [("
        field_5a", "field_5b"), "field_6"], "classes": ["wide","collapse"]}),
                (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,]     ...

            # allow save as new
            save_as = True
    • 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)
  • Fieldsets
    • classes
      • default
        • wide, collapsed
        • lib/python2.7/site-packages/django/contrib/admin/static/admin/css/forms.css
          • /* WIDE FIELDSETS */
          • /* COLLAPSED FIELDSETS */
      • custom widget:
        • mysite/myapp/admin.py
          • from django.contrib.gis.db import models

            class MyModelAdmin(admin.ModelAdmin):

                formfield_overrides = {
                    models.TextField: {"widget": widgets.Textarea(attrs={"rows": "1"})},
                }
      • custom css:
        • How to adjust grid in Django's fieldset.html
        • mysite/myproject/settings.py
          • # additional source for statics
            STATICFILES_DIRS = (
              os.path.join(BASE_DIR, "static_general"),
            )
        • mysite/myapp/admin.py
          • class StackDefaultsAdmin(SingletonModelAdmin):
                # detail
                fieldsets = [
                         (_('My Group'), {'fields': ['myfield',], 'classes': ['wide','extra_wide_field']}),
                     ]
                # css for class extra_wide_field
                class Media:
                    css = {
                        'all': ('admin/css/my.css',)
                    }
        • mysite/static_general/admin/css/my.css
          • /* EXTRA WIDE FIELDSETS */

            .extra_wide_field input.vTextField {
                width: 40em;
            }
    • ...
  • Llista / List
    • list_display
      • Nombre d'elements per pàgina / Number of item per page
      • Dates amb més precisió / Dates with more precision
        • Date Format in Django Admin
        • settings.py
          • # get more precision in admin list_display dates
            precise_date_format = "d b Y H:i:s"

            from django.conf.locale.ca import formats as ca_formats
            ca_formats.DATETIME_FORMAT = precise_date_format
            from django.conf.locale.en import formats as en_formats
            en_formats.DATETIME_FORMAT = precise_date_format
            from django.conf.locale.es import formats as es_formats
            es_formats.DATETIME_FORMAT = precise_date_format
            from django.conf.locale.fr import formats as fr_formats
            fr_formats.DATETIME_FORMAT = precise_date_format

      • html per als valors d'una columna
        • list_display = ["my_col"]

          def my_col(self, obj):
              return obj.myfield
          my_col.short_description = "My column"
          my_col.allow_tags = True
        • list_display = ["my_col"]

          def my_col(self, obj):
              target = getattr(obj, "my_url_field")
              if target:
                  return '<a href="%s">%s</a>' % (target, target)
              else:
                  return None

          my_col.short_description = "My column"
          my_col.allow_tags = True
      • Mostra només els objectes de l'usuari / Show only objects of user
      • Afegeix columnes / Add columns
      • ManyToManyField
      • ForeignKey
    • list_filter
  • 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,]
      • Link to modify object
      • 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
      • Multiple fk
        • when several fk exist from inlined object to present one, you must choose one:
          • fk_name = '...'
      • List of links to fk objects instead of objects themselves
        • ...
      • List of links to m2m objects instead of objects themselves
        • class ClassroomPersonInline(admin.TabularInline):
              model = Classroom.members.through
              verbose_name = _("Membership")
              verbose_name_plural = _("Memberships")
              raw_id_fields = ("person",)
      • Problemes / Problems
  • Delete
  • Two-step (as in user creation: edit the just added object)
    • Accions: pàgina de confirmació / Actions: confirmation page
    • Filter by date
    • 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
    • django-admin-csvexport
      • ...
      • Problemes
        • 2.2: només es poden seleccionar els camps si l'usuari té permisos d'administració
  • 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
    • AdminSite attributes (dev version)
    • Customizing the Django Admin (slides)
    • Reverse admin
      • admin:...
    • Extra context (e.g. a url for a button in a detail page)
      • admin.py
        • class MyModelAdmin(admin.ModelAdmin):

              def change_view(self, request, object_id, form_url="", extra_context=None):
                  # https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.changelist_view
                  extra_context = extra_context or {}
             
                  # complete context with my_url
                  device_stats_url = ""
                  obj = self.get_object(request, object_id)
                 
                  extra_context["my_url"] = obj...
             
                  return super().change_view(
                      request,
                      object_id,
                      form_url,
                      extra_context=extra_context,
                  )
      • my_project/my_app/templates/admin/my_app/my_model/change_form.html
        • {% extends "admin/change_form.html" %}
          {% load i18n %}

          {% block object-tools-items %}
            <li><a href="{{ my_url }}">{% trans "Go to url button" %}</a></li>
            {{ block.super }}
          {% endblock %}
    • Plantilles / Templates
      • ...


        default custom

        rendered by template blocks admin.py template


        admin/base_site.html
        • title
        • branding
        • nav-global


        list ModelAdmin
        admin/change_list.html
        • extrastyle
        • extrahead
        • content
          • object-tools
            • object-tools-items (add custom list page buttons here)
          • search
          • date_hierarchy
          • result_list
          • filters
        • class MyModelAdmin(admin.ModelAdmin):
              # list
              # no need to specify if you put html template file in the canonical dir
              change_list_template = "admin/my_app/my_model/change_list.html"
        canonical dir: my_project/my_app/templates/admin/my_app/my_model/change_list.html
        • {% extends "admin/change_list.html" %}
          {% load i18n %}

          {% block object-tools-items %}
            <li>...</li>
            ...
            {{ block.super }}
          {% endblock %}
        detail ModelAdmin.
        admin/change_form.html
        • extrastyle
        • coltype
        • bodyclass
        • breadcrumbs
        • content
          • object-tools
            • object-tools-items (add custom detail page buttons here)
        • ...
        • class MyModelAdmin(admin.ModelAdmin):
              # detail

              # no need to specify if you put html template file in the canonical dir?

              change_form_template = "admin/my_app/my_model/change_form.html"


              # NOTE: we must overwrite render_change_form instead of simply set change_form_template because of django-mass-edit
              # https://github.com/burke-software/django-mass-edit/issues/82
              def render_change_form(self, *args, **kwargs):
                  self.change_form_template =
          "admin/my_app/my_model/change_form.html"
                  return super().render_change_form(*args, **kwargs)   
        canonical dir: my_project/my_app/templates/admin/my_app/my_model/change_form.html
        • {% extends "admin/change_form.html" %}
          {% load i18n %}

          {% block object-tools-items %}
            <li>...</li>
            ...
            {{ block.super }}
          {% endblock %}
        fieldset




        field inline
        admin/edit_inline/stacked.html -



        admin/edit_inline/tabular.html -

    • Descripcions / Descriptions


      list page
      • add block "description" to your project-wide template my_project/templates/admin/change_list.html
        • {% extends "admin/change_list.html" %}

          {% block content %}
            {% block description %}
            {% endblock %}
           
            {{ block.super }}
          {% endblock %}
      • use new block "description" from your app template in my_project/my_app/templates/admin/my_app/my_model/change_list.html (it will extend your project-wide template)
        • {% extends "admin/change_list.html" %}
          {% load i18n %}

          {% block description %}
          Description of mymodel
          {% endblock %}
      detail page
      • add block "description" to your project-wide template my_project/templates/admin/change_form.html
        • {% extends "admin/change_form.html" %}

          {% block content %}
            {% block description %}
            {% endblock %}
           
            {{ block.super }}
          {% endblock %}
      • use new block "description" from your app template in my_project/my_app/templates/admin/my_app/my_model/change_form.html (it will extend your project-wide template)
        • {% extends "admin/change_form.html" %}
          {% load i18n %}

          {% block description %}
          Description of mymodel
          {% endblock %}
      detail fieldset fieldsets = [
          (..., "description": "description for this fieldset")
      ]
      detail field
      help from field in models.py: help_text
      inline block ...
      description or href of an inline block
      • admin.py
        • class MyOtherModel(admin.TabularInline):
              template = "admin/my_app/my_other_model_inline/tabular_help_myhelp.html"
      • create a new block in template my_app/templates/admin/edit_inline/tabular_help.html, based on system admin/edit_inline/tabular.html:
        • ...
          <fieldset class="module {{ inline_admin_formset.classes }}">
             <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
             {{ inline_admin_formset.formset.non_form_errors }}
             {% block help-region %}
             {% endblock %}
             <table>
          ...
      • my_app/templates/admin/edit_inline/tabular_help_myhelp.html
        • {% extends "admin/edit_inline/tabular_help.html" %}
          {% load i18n static %}

          {% block help-region %}
            <a href="{% static 'myhelp.html' %}">{% trans "Help" %}</a>
            {{ block.super }}
          {% endblock %}
      • my_app/static/myhelp.html
        • ...
    • 5 ways to make Django Admin safer
      • Change the URL
      • Visually distinguish environments
        • mkdir -p my_project/templates/admin
        • cp -pr .../lib/python2.7/site-packages/django/contrib/admin/templates/admin/base_site.htmlmy_project/templates/admin
        • my_project/templates/admin/base_site.html
          • {% extends "admin/base.html" %}

            {% block extrastyle %}
            <style type="text/css">
                body:before {
                    display: block;
                    line-height: 35px;
                    text-align: center;
                    font-weight: bold;
                    text-transform: uppercase;
                    color: yellow;
                    content: "{{ ENVIRONMENT_NAME }}";
                    background-color: {{ ENVIRONMENT_COLOUR }};
                }
            </style>
            {% endblock %}

            {% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}

            {% block branding %}
            <h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1>
            {% endblock %}

            {% block nav-global %}{% endblock %}
        • my_project/context_processors.py
          • from django.conf import settings

            def from_settings(request):
                return {
                    'ENVIRONMENT_NAME': settings.DEPLOYMENT_ENVIRONMENT_NAME,
                    'ENVIRONMENT_COLOUR': settings.DEPLOYMENT_ENVIRONMENT_COLOUR,
                }
        • my_project/my_project/settings.py
          • DEPLOYMENT_ENVIRONMENT_NAME = 'production'
            DEPLOYMENT_ENVIRONMENT_COLOUR = 'red'

            TEMPLATES = [
                {
                    'BACKEND': 'django.template.backends.django.DjangoTemplates',
                    'DIRS': [os.path.join(BASE_DIR, 'templates')],
                    'APP_DIRS': True,
                    'OPTIONS': {
                        'context_processors': [
                            'django.template.context_processors.debug',
                            'django.template.context_processors.request',
                            'django.contrib.auth.context_processors.auth',
                            'django.contrib.messages.context_processors.messages',
                            'context_processors.from_settings',
                        ],
                    },
                },
            ]
      • Name your Admin site
      • Separate the Django Admin from the main site
      • Add Two Factor Authentication (2FA)
    • Exemple d'admin
      • Customize the admin look and feel
      • Customize the admin index page
      • Només un fitxer:
        • mkdir -p my_project/templates/admin
        • source env/bin/activate; python -c "import django; print(django.__path__)"
        • cp -pr /opt/PYTHON27/lib/python2.7/site-packages/django/contrib/admin/templates/admin/base_site.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
      • modifiqueu 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 porta a una llista d'un altre model, filtrat per l'id del model actual:
            • admin.py
              from django.utils.html import format_html
              from django.urls import reverse

              @admin.register(MyModel)
              class MyModelAdmin(admin.ModelAdmin):
                  # list
                  list_display = (...,"additional_button",...)

                  def additional_button(self, item):
                      mymodel_id = getattr(item, "id")
                      result = format_html(
                          '<a class="button" href="{url}?mymodel__id__exact={id}">Other model</a>'.format(
                              url=reverse("admin:my_app_my_model_changelist"),
                              id=mymodel_id,
                          )
                      )

                      return result

                  additional_button.short_description = "Other model"
                  additional_button.allow_tags = True
          • 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 (al costat del botó per a afegir un element) / General in the list (near the button to add an element)
          • 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 = [
                          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/my_app/import_template.html', context)

              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 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/my_model/
            from django.contrib import admin, messages
            from django.conf.urls import url

            from django.core.urlresolvers import reverse @admin.register(MyModel)

            class MyModelAdmin(admin.ModelAdmin):
                model = MyModel

                # rendered at django.contrib.admin.options.ModelAdmin.changelist_view
                change_list_template = 'admin/my_app/my_model/change_list_apply_template.html'
            change_list.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 was 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:
                        # Django < 1.8
                        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'^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)


        • En el detall d'un element
          • Afegir un botó que porti a una altra part de l'admin (llista dels altres objected que estan relacionats amb el meu via FK)
            • ...
              admin.py my_app/templates/admin/my_app/my_model/
              from django.contrib import admin, messages
              from django.conf.urls import url

              from django.core.urlresolvers import reverse

              @admin.register(MyModel)
              class MyModelAdmin(admin.ModelAdmin):
                  model = MyModel
                 
                  # rendered at django.contrib.admin.options.ModelAdmin.render_change_form
                 
                  change_form_template = 'admin/my_app/my_model/change_form.html'
              change_form.html
              • {% extends "admin/change_form.html" %}
                {% load i18n %}

                {% block object-tools-items %}
                  <li><a href={% url 'admin:my_app_my_other_model_changelist' %}?relation_to_mymodel__id__exact={{ object_id }}>{% trans "My other model" %}</a></li>
                  {{ block.super }}
                {% endblock %}

          • Afegir un botó que obri una pàgina intermèdia, recuperi informació i faci alguna acció
            • Django Admin Actions on single object
            • admin.py my_app/templates/admin/my_app/my_model/
              class MyModel(admin.ModelAdmin):
                  model = MyModel
                 
                  # rendered at django.contrib.admin.options.ModelAdmin.render_change_form
                  change_form_template = "admin/my_app/my_model/change_form.html"
              change_form.html
              • {% extends "admin/change_form.html" %}
                {% load i18n %}
                {% load admin_urls %}

                {% block object-tools-items %}
                  <li><a href="{% url opts|admin_urlname:"my_action_intermediate_view" object_id %}">{% trans "Perform my action" %}</a></li>
                  {{ block.super }}
                {% endblock %}
                  def get_model_info(self):
                      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(MediaItemAdmin, self).get_urls()
                      info = self.get_model_info()
                      my_urls = [
                          url(
                              r"^(?P<pk>\d+)/my_action_intermediate_view/$",
                              self.admin_site.admin_view(self.my_action_intermediate_view),
                              name="%s_%s_my_action_intermediate_view" % info,
                          ),
                          url(
                              r"^(?P<pk>\d+)/my_action_view/$",
                              self.admin_site.admin_view(self.my_action_view),
                              name="%s_%s_my_action_view" % info,
                          ),
                      ]
                      return my_urls + urls

                  def my_action_intermediate_view(self, request, *args, **kwargs):
                      active_object = self.get_object(request, kwargs["pk"])
                     
                      context = {}
                      context["opts"] = self.model._meta
                      context["object_id"] = kwargs["pk"]
                      context["active_object"] = active_object

                      return TemplateResponse(
                          request,
                          "admin/my_app/my_model/my_action_template.html",
                          context,
                      )
              my_action_template.html
              • {% extends "admin/base_site.html" %}
                {% load i18n l10n admin_urls static %}

                {% block extrahead %}
                {{ block.super }}
                {{ media }}
                <script type="text/javascript" src="{% static 'admin/js/cancel.js' %}"></script>
                {% endblock %}

                {% block content %}
                <p>
                  {% blocktrans %}Do something for: {{ active_object }}
                  {% endblocktrans %}
                </p>

                <form action="{% url opts|admin_urlname:"my_action_view" object_id %}" method="post">{% csrf_token %}
                  <div>
                    <label for="my_param">My param:</label><br>
                    <input type="text" id="my_param" name="my_param"><br>

                    <input type="submit" name="apply" value="{% trans "Go" %}"/>
                  </div>
                </form>

                {% endblock %}
                  def my_action_view(self, request, *args, **kwargs):
                      # custom view which should return an HttpResponse
                      instance = self.get_object(request, kwargs["pk"])

                      # do something:
                      my_param = request.POST.get("my_param", "")
                      if my_param:
                          instance.do_something(my_param)

                          success_message = _("Successfully applied")
                          messages.success(request, success_message)
                      else:
                          error_message = _("Empty my_param")
                          messages.error(request, error_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
      • Pàgina de confirmació / Confirmation page
        • Custom django admin actions with an intermediate page
        • Django Admin Action Confirmation Page
        • The default “delete selected” admin action in Django
        • Exemple / Example
          • my_app/admin.py
            • from django.shortcuts import render
              from django.http import HttpResponseRedirect

              class MyModelAdmin(admin.ModelAdmin):
                  actions = ['my_action']

                  def my_action(self, request, queryset):
                      # perform the action
                      if 'apply' in request.POST:
                          # The user clicked submit on the intermediate form.

              # NOTE: a fallback "False" is needed because unchecked checkboxes will not be sent at all from html form # then they are converted from string to boolean
              dry_run = eval(request.POST.get("dry_run", "False"))
                         
              for instance in queryset:
                              try:
                                  do_something_result = instance.do_something()
                                  self.message_user(request, 'Result of action performed on: {} -> {}'.format(instance, do_something_result))
                              except Exception as e:
                                  self.message_user(request, 'Could not perform action on: {} ({})'.format(instance, e),
                                                    level=messages.ERROR)
                          return HttpResponseRedirect(request.get_full_path())
                         
                      # confirmation page
                      context = {'queryset':queryset}
                      return render(request, 'admin/my_app/my_action_confirmation_template.html', context)
                         
                  my_action.short_description = _("Perform action on selected items")
          • my_app/templates/admin/my_app/my_action_confirmation_template.html
            • {% extends "admin/base_site.html" %}
              {% load i18n l10n admin_urls static %}

              {% block extrahead %}
                  {{ block.super }}
                  {{ media }}
                  <script type="text/javascript" src="{% static 'admin/js/cancel.js' %}"></script>
              {% endblock %}

              {% block content %}
              <p>
              {% blocktrans %}Are you sure you want to perform the action on these items?{% endblocktrans %}
              </p>

              <form action="" method="post">{% csrf_token %}
              <div>
                <ul> 
                {% for obj in queryset %}
                  <li>{{ obj }}</li>
                  <!-- the following line must be present in the loop, even if you do not use the previous one, so as the queryset is preserved -->
                  <input type="hidden" name="_selected_action" value="{{ obj.pk }}" />
                {% endfor %}
                </ul>

                <!-- action value must match the name given in MyModelAdmin, so as form action="" redirects to the same action method that called this -->
                <input type="hidden" name="action" value="my_action" />

                <input type="submit" name="apply" value="{% trans "Yes, I'm sure" %}"/>
              </div>
              </form>

              {% endblock %}
      • 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)
    • Horizontal or vertical interface (two boxes)
    • 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
        • ...
    • 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
    • Searchable (in a pop-up)
      • raw_id_fields = ('field_1',)
    • 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}
  • GenericForeignKey
    • from model that has GenericForeignKey()
      • admin.py 
        • class MyModelAdmin(admin.ModelAdmin):
              fields = ('content_type','content_id',)
             
              def formfield_for_foreignkey(self, db_field, request, **kwargs):
                  if db_field.name == "content_type":
                      # only allowed content types: MyFirstModel, MySecondModel
                      kwargs["queryset"] = ContentType.objects.filter(model__in=['myfirstmodel','mysecondmodel',])
                  return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
      • Magnifying glass to be able to search for related objects
        • forms.py
          • from django.forms import ModelForm
            from django.contrib.admin.widgets import ForeignKeyRawIdWidget
            from django.contrib.admin.sites import site
            from django.db.models.fields.related import ManyToOneRel

            from .models import MyModel


            class MyModelAdminForm(ModelForm):
                def __init__(self, *args, **kwargs):
                    super(MyModelAdminForm, self).__init__(*args, **kwargs)
                    try:
                        model = self.instance.content_type.model_class()
                        model_key = model._meta.pk.name
                    except:
                        # when creating, we do not have a content_type yet
                        model = MyDefaultRelatedModel
                        model_key = "id"
                    rel = ManyToOneRel(model._meta.get_field("id"), model, model_key)
                    self.fields["object_id"].widget = ForeignKeyRawIdWidget(rel, site)

                class Meta(object):
                    model = MyModel
                    fields = "__all__"
        • admin.py
          • from my_app.forms import MyModelAdminForm

            class MyModelAdmin(admin.ModelAdmin):
                form = MyModelAdminForm
    • from model that has not GenericForeignKey()
      • Using generic relations as an inline
        • admin.py
          • from django.contrib.contenttypes.admin import GenericTabularInline

            class TaggedItemInline(GenericTabularInline):
                model = TaggedItem
                # show a link to go to the TaggedItem from BookmarkAdmin
                show_change_link = True

            class BookmarkAdmin(admin.ModelAdmin):
                ...
                inlines = [TaggedItemInline,]
      • ...
    • Admin widget for generic relations in django (code that has some errors)
    • django-genericadmin
    • django-genericrelationview
    • django-admin-genericfk (does not work with Django 1.9)
  • 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
      • nova llengua / new language
        • si la llengua que voleu no està a la llista LANG_INFO (django/conf/locale/__init__.py), caldrà afegir-la als settings:
      • 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
      • això generarà els fitxers .po:
      • then translate locale/ca/LC_MESSAGES/django.po (e.g. using lokalize)
        • lokalize
          • Project / New project
            • my_project/locale/index.lokalize
          • django.po
      • workaround for non existing ignore option for compilemessages
        • cd src/myproject/myproject
        • ../manage.py compilemessages
      • django-admin.py compilemessages
    • següents vegades / next times:
      • cd project_name
      • ./manage.py makemessages -a --ignore="env*"
      • django-admin.py makemessages -a
      • translate locale/ca/LC_MESSAGES/django.po (e.g. using lokalize)
      • workaround for non existing ignore option for compilemessages
        • cd src/myproject/myproject
        • ../manage.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
      • assumint que el camp amb traduccions es diu «name»
        • el camp «name» ja no serà accessible
        • la llengua per defecte és:
        • un cop determinada la llengua (si no s'especifica, es considera que xx és la llengua per defecte), el camp «name» queda temporalment enllaçat cap a «name_xx»
        • la llengua es pot forçar:
          • fora de les vistes:
            • translation.activate('ca')
              # will be the same as name_ca:
              myobject.name
            • # you can directly access the translated version, without setting the language
              myobject.name_ca
          • a les vistes:
            • s'agafarà el valor que li arriba amb la capçalera Accept-Language (si no s'especifica, s'agafarà la llengua per defecte)
            • als testos unitaris:
              • res = self.client.get(
                    url,
                    **{'HTTP_ACCEPT_LANGUAGE': lang}
                    )
        • ...
      • Management commands
        • quan la base de dades ja existeix i s'afegeix la internacionalització d'un camp, cal poblar, almenys, el camp_xx, on xx és la llengua per defecte /  for existing objects when modeltranslation is added:
          • python manage.py update_translation_fields
          • to populate other languages than the default one:
            • python manage.py update_translation_fields --language=ca
          • to populate only fields in models in a specific app:
            • python manage.py update_translation_fields myapp
          • to populate only fields in specific model:
            • python manage.py update_translation_fields myapp mymodel
      • get active language:
        • import modeltranslation
          modeltranslation.utils.get_language()
      • set active language (e.g. outside a view):
        • from django.utils import translation
          translation.activate('ca')
      • unset active language
        • translation.deactivate()
      • 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)
          • from django.utils import translation
            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 (parallel to models.py, where translatable fields are defined)
            • 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

  • Python unit tests
  • Testing in Django
    • Tutorial part 5
    • Integration with coverage.py
    • Overriding settings
      • from django.test import override_settings

        @override_settings(...)
        def ...
    • Run tests
      • ./manage.py test
        • parallel
          • pip install tblib
          • ./manage.py test --parallel
      • ./manage.py test myapp
      • ./manage.py test myapp....
      • ./manage.py test --settings ... --verbose 3
      • From Eclipse
    • Email
      • Testing tools: Email services
      • mytest.py
        • from django.core import mail

          class MyFirstTests(TestCase):
              def my_first_test(self):
                  from django.core import mail
                  ...
                  # verify the number of sent messages
                  self.assertEqual(len(mail.outbox), 1)

                  # verify that the subject of the first message is correct
                  self.assertEqual(mail.outbox[0].subject, ...)
    • PostgreSQL
    • Excepcions / Exceptions
      • Integrity error and atomic transactions
      • from django.db.utils import IntegrityError
        from django.db import transaction

        def test_call_to_method_that_should_raise_an_exception(self):
            with transaction.atomic():
                with self.assertRaisesRegex(IntegrityError, 'duplicate key value violates unique constraint'):
                    # put your call to method that should raise an IntegrityError exception
    • Djangorestframework
      • Exemples / Examples
        • from rest_framework.test import APITestCase
          from rest_framework import status

          class MyTestAPITestCase(APITestCase):
              # define user for subsequent authenticated requests
              self.client.force_authenticate(my_user)

              # get
              res = self.client.get(url, query_params_dict)

              # get with request headers
              headers = {...}
              res =
          self.client.get(url, post_params_dict, headers=headers)    
              # get with preferred language
             
          res = self.client.get(url, post_params_dict, **{"HTTP_ACCEPT_LANGUAGE": "ca"})

              # cors headers in preflight (django-cors-headers)
             
          res = self.client.options(url,         **{"HTTP_ACCESS_CONTROL_REQUEST_METHOD": "GET"},
                  **{"HTTP_ORIGIN": "http://localhost/"},
              )

              # post (multipart)
              res = self.client.post(url, post_params_dict)

              # post (json)
              res = self.client.post(url, post_params_dict, format="json")

              # print result
              print("{} {}".format(res.status_code, res.data))
              print("response headers: {}".format(res._headers))

              # verify result
              self.assertEqual(res.status_code, status.HTTP_200_OK)
              self.assertEqual(res.data[])
      • None in payload
        • when trying to set a related field (declared using allow_null=True) to None
          • Note that (implicit) PrimaryKeyRelatedField has allow_null=True
            • see RelatedField.run_validation()
        • Django REST Framework's APIClient sends None as 'None'
          • Solució / Solution
            • using JSON:
              • res = self.client.post(..., {"myfield":None}, format="json")
            • using multipart:
              • res = self.client.post(..., {"myfield":""})
              • res = self.client.post(..., {"myfield":""}, format="multipart")
      • list in a comma-separated queryparam
        • url tests.py views.py
          myparam=first,second,third res = self.client.get(url, {"myparam": ",".join(myparam_list)})     my_param = openapi.Parameter(
                  "myparam",
                  openapi.IN_QUERY,
                  description="...",
                  type=openapi.TYPE_ARRAY,
                  items=openapi.Items(type=openapi.TYPE_STRING),
                  )
              @swagger_auto_schema(manual_parameters=[my_param])
              def my_view(...)
                 
          myparam_list = request.query_params["myparam"].split(",")
          myparam_lists = request.query_params.getlist("myparam")
          # put myparam=first,second&myparam=third into a single list
          myparam_list = []
          for myparam_list_element in myparam_lists:
              myparam_list += myparam_list_element.split(",")
          myparam=first&myparam=second&myparam=third res = self.client.get(url, {"myparam": myparam_list}) myparam_list = request.query_params.getlist("myparam")
        • views.py
          • myparam_list = request.query_params.getlist("myparam")
            #
            request.query_params["myparam"] returns only the last value: "second"
        • test.py
          • myparam_list = ["first", "second"]
            # <url>?myparam=first,second
            res = self.client.get(url, {"myparam": myparam_list})
      • empty file
        • when trying to upload an empty file to a field declared with allow_empty_file=True:
          • my_attached_file = serializers.FileField(write_only=True, required=False, allow_empty_file=True)
        • Solució / Solution:
          • res = ...
    • FileField, ImageField, ...
      • database
        • from django.test import TestCase
          from django.core.files.uploadedfile import SimpleUploadedFile

          class MyTests(TestCase):
              def test_mymodel_create(self):
                  mymodel = MyModel.objects.create(
                      name = 'My first instance',
                      image = SimpleUploadedFile(name='test_image.png', content=open(image_path, 'rb').read(), content_type='image/png'),
                  )
      • djangorestframework
        • from rest_framework.test import APITestCase

          class MyAPITests(APITestCase):
              def test_mymodel_api_create(self):
                  with open('test_image.png') as fp:
                      res = self.client.post(url, {'name': 'My first instance', 'image': fp})
    • Dades inicials en tests unitaris / Initial dataset in unit tests
    • 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)
  • CRUD

  • url (Swagger) urls.py myapp/views.py (reverse)
    myapp/tests/test_mymodel.py ./manage test myapp.tests.test_mymodel.
    database



    from django.test import TestCase

    class MymodelTests(TestCase):
        """ Test Mymodel in database """
        def setUp(self):
            ...
    MymodelTests




        def test_mymodel_create(): MymodelTests.test_mymodel_create




        def test_mymodel_list(): MymodelTests.test_mymodel_list




        def test_mymodel_retrieve(): MymodelTests.test_mymodel_retrieve




        def test_mymodel_update(): MymodelTests.test_mymodel_update




        def test_mymodel_delete(): MymodelTests.test_mymodel_delete


    # myproject/urls.py

    urlpatterns = [
        url(r'^mynamespace/', include('myapp.urls', namespace='mynamespace_myapp')),
    ]




    not nested views



    from django.contrib.auth.models import User
    from rest_framework.test import APITestCase

    class MymodelAPITests(APITestCase):
        """ Test Mymodel through API """

        def setUp(self):
            self.user = User.objects.create_user()
            self.client.force_authenticate(self.user)
    MymodelAPITests
    /mynamespace/mymodelprefix/ # myapp/urls.py

    from rest_framework import routers

    mymodel_router = routers.SimpleRouter()
    mymodel_router.register(r'mymodelprefix', views.MymodelViewset)

    urlpatterns = [
        url(r'^', include(mymodel_router.urls)),
    ]
    class MymodelViewSet(viewsets.ModelViewSet):
        queryset = Mymodel.objects.all()
        serializer_class = MymodelSerializer
        lookup_field = 'fieldc'
    MYMODEL_LIST_API_URL = lambda: reverse(
        'mynamespace:mymodel-list'
    )
        def test_mymodel_api_create(self):
            res = self.client.post(
                MYMODEL_LIST_API_URL(), {<mymodel_fields>}
            )
    MymodelAPITests.test_mymodel_api_create
        def test_mymodel_api_list(self):
            res = self.client.get(
                MYMODEL_LIST_API_URL(), {<queryparams>}
            )
    MymodelAPITests.test_mymodel_api_list
    /mynamespace/mymodelprefix/{fieldc}/ MYMODEL_DETAIL_API_URL = lambda mymodel_field1: reverse(
        'mynamespace:mymodel-detail', args=(mymodel_field1,)
    )
        def test_mymodel_api_retrieve(self):
            # mymodel_field1: value of field used to lookup (e.g. uuid)
            res = self.client.get(
                MYMODEL_DETAIL_API_URL(mymodel_field1), {<queryparams>}
            )
    MymodelAPITests.test_mymodel_api_retrieve
        def test_mymodel_api_update(self):
            # mymodel_field1: value of field used to lookup (e.g. uuid)
            res = self.client.patch(
                MYMODEL_DETAIL_API_URL(mymodel_field1), {<mymodel_fields>}
            )
    MymodelAPITests.test_mymodel_api_update
        def test_mymodel_api_delete(self):
            # mymodel_field1: value of field used to lookup (e.g. uuid)
            res = self.client.delete(
                MYMODEL_DETAIL_API_URL(mymodel_field1)
            )
    MymodelAPITests.test_mymodel_api_delete
    /mynamespace/mymodelprefix/{fieldc}/custom_view/ class MymodelViewSet(viewsets.ModelViewSet):

        @action(detail=True, methods=['get'])
        def custom_view(self, request, *args, **kwargs):
    MYMODEL_CUSTOMVIEW_API_URL = lambda mymodel_field1: reverse(
        'mynamespace:mymodel-custom-view', args=(mymodel_field1,)
    )
        def test_mymodel_api_customview(self):
            # mymodel_field1: value of field used to lookup (e.g. uuid)
            res = self.client.get(
                MYMODEL_CUSTOMVIEW_API_URL(mymodel_field1), {<queryparams>}
            )

    nested views



    from django.contrib.auth.models import User
    from rest_framework.test import APITestCase

    class ParentmodelMymodelAPITests(APITestCase):
        """ Test Mymodel through nested API """

        def setUp(self):
            self.user = User.objects.create_user()
            self.client.force_authenticate(self.user)
    ParentmodelMymodelAPITests
    /mynamespace/parentmodelprefix/{pmodel_fieldp}/mymodelprefix/ # myapp/urls.py

    from rest_framework_nested import routers

    parentmodel_router = routers.SimpleRouter()
    parentmodel_router.register(r'parentmodelprefix', views.ParentmodelViewSet)

    parentmodel_mymodel_router = routers.NestedSimpleRouter(parentmodel_router, r'parentmodelprefix', lookup='pmodel')
    parentmodel_mymodel_router.register(r'mymodelprefix', views.MymodelParentmodelViewSet )
    #parentmodel_mymodel_router.register(r'mymodelprefix', views.MymodelParentmodelViewSet, basename='mybasename')

    urlpatterns = [
        url(r'^', include(mymodel_router.urls)),
    ]
    class ParentmodelViewSet(viewsets.ModelViewSet):
        queryset = Parentmodel.objects.all()
        serializer_class = ParentmodelSerializer
        lookup_field = 'fieldp'

    class MymodelParentmodelViewSet(viewsets.ModelViewSet):
        queryset = Mymodel.objects.all()
        serializer_class = MymodelSerializer
        lookup_field = 'fieldc'

        # used by list, retrieve, update, partial_update, destroy
        def get_queryset(self):
            # only entries that belong to specified parent
            parentmodel_fieldp = self.kwargs.get('pmodel_fieldp', None)
            parent_object = get_object_or_404(Parentmodel, fieldp=parentmodel_fieldp)
            queryset = Mymodel.objects.filter(field_to_parent=parent_object)
            return queryset

        # used by create
        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
            parentmodel_fieldp = self.kwargs.get('pmodel_fieldp', None)
            parent_object = get_object_or_404(Parentmodel, fieldp=parentmodel_fieldp)
            serializer.save(field_to_parent=parent_object)
    PARENTMODEL_MYMODEL_LIST_API_URL = lambda parentmodel_fieldp: reverse(
        'mynamespace:mymodel-list', args=(parentmodel_fieldp,)
    )
    #PARENTMODEL_MYMODEL_LIST_API_URL = lambda parentmodel_fieldp: reverse(
        'mynamespace:mybasename-list', args=(parentmodel_fieldp,)
    )
        def test_mymodel_nested_api_create(self):
            # parentmodel_field1: value of field used to lookup Parentmodel (e.g. uuid)
            res = self.client.post(
                PARENTMODEL_MYMODEL_LIST_API_URL(parentmodel_fieldp), {<mymodel_fields>}
            )
    ParentmodelMymodelAPITests.test_mymodel_nested_api_create
        def test_mymodel_nested_api_list(self):
            # parentmodel_field1: value of field used to lookup Parentmodel (e.g. uuid)
            res = self.client.get(
                PARENTMODEL_MYMODEL_LIST_API_URL(parentmodel_fieldp), {<queryparams>}
            )
    ParentmodelMymodelAPITests.test_mymodel_nested_api_list
    /mynamespace/parentmodelprefix/{pmodel_fieldp}/mymodelprefix/{fieldc}/
    PARENTMODEL_MYMODEL_DETAIL_API_URL = lambda parentmodel_fieldp, mymodel_fieldc: reverse(
        'mynamespace:mymodel-detail', args=(parentmodel_fieldp, mymodel_fieldc,)
    )
    #PARENTMODEL_MYMODEL_DETAIL_API_URL = lambda parentmodel_fieldp, mymodel_fieldc: reverse(
        'mynamespace:mybasename-detail', args=(parentmodel_fieldp, mymodel_fieldc,)
    )
        def test_mymodel_nested_api_retrieve(self):
            # parentmodel_field1: value of field used to lookup Parentmodel (e.g. uuid)
            # mymodel_field1: value of field used to lookup (e.g. uuid)
            res = self.client.get(
                PARENTMODEL_MYMODEL_DETAIL_API_URL(parentmodel_fieldp, mymodel_fieldc), {<queryparams>}
            )
    ParentmodelMymodelAPITests.test_mymodel_nested_api_retrieve
        def test_mymodel_nested_api_update(self):
            # parentmodel_field1: value of field used to lookup Parentmodel (e.g. uuid)
            # mymodel_field1: value of field used to lookup (e.g. uuid)
            res = self.client.patch(
                PARENTMODEL_MYMODEL_DETAIL_API_URL(parentmodel_fieldp, mymodel_fieldc), {<mymodel_fields>}
            )
    ParentmodelMymodelAPITests.test_mymodel_nested_api_update
        def test_mymodel_nested_api_delete(self):
            # parentmodel_field1: value of field used to lookup Parentmodel (e.g. uuid)
            # mymodel_field1: value of field used to lookup (e.g. uuid)
            res = self.client.delete(
                PARENTMODEL_MYMODEL_DETAIL_API_URL(parentmodel_fieldp, mymodel_fieldc)
            )
    ParentmodelMymodelAPITests.test_mymodel_nested_api_delete
    /mynamespace/parentmodelprefix/{pmodel_fieldp}/mymodelprefix/{fieldc}/custom_view/
        @action(detail=True, methods=['get'])
        def custom_view(self, request, *args, **kwargs):
    PARENTMODEL_MYMODEL_CUSTOMVIEW_API_URL = lambda parentmodel_fieldp, mymodel_fieldc: reverse(
        'mynamespace:mymodel-custom-view', args=(parentmodel_fieldp, mymodel_fieldc,)
    )
        def test_mymodel_api_customview(self):
            # parentmodel_field1: value of field used to lookup Parentmodel (e.g. uuid)
            # mymodel_field1: value of field used to lookup Mymodel (e.g. uuid)
            res = self.client.get(
                PARENTMODEL_MYMODEL_CUSTOMVIEW_API_URL(parentmodel_fieldp, mymodel_fieldc), {<queryparams>}
            )
    ParentmodelMymodelAPITests.test_mymodel_nested_api_customview
  • Mock

    • function definition function call
      (we are mocking this function call;
      not the function definition)
      function test (same value is mocked every time) function test (different mocked values every time, or exceptions)

      def my_function(...):
          return my_value
      def my_caller_function(...):
          returned_value = my_function()
      from mock import patch

      @patch('path.to.my_function')
      def test_my_caller_function(..., mock_my_function):
          mock_my_function.return_value = my_fake_value
      from mock import patch

      @patch('path.to.my_function')
      def test_my_caller_function(..., mock_my_function):
          mock_my_function.side_effect = [my_fake_value_a, my_fake_value_b]

      def my_function(...):
          my_object.my_attr_1 = my_value_1
          my_object.my_attr_2 = my_value_2
          return my_object
      def my_caller_function(..):
          returned_object = my_function()
      from mock import patch, Mock

      @patch('path.to.my_function')
      def test_my_caller_function(..., mock_my_function):
          mock_my_function.return_value = Mock(my_attr_1=my_fake_value1, my_attr_2=my_fake_value2)
      from mock import patch

      @patch('path.to.my_function')
      def test_my_caller_function(..., mock_my_function):
          mock_my_function.side_effect = Exception("not working")

      (my_app/models.py)

      class MyModel():
          def my_method(...)

      #@patch("my_app.MyModel.my_method")
      @patch("my_app.models.MyModel.my_method")
      def test_my_caller_function(..., mock_my_model_my_method):
      ...

      requests (standard requests module) (my_dir/my_file.py)

      import requests

      class MyClass(object):
          def my_caller_function(self, ...):
              res = requests.post(...)
      from mock import patch, Mock

      # NO: @patch('my_dir.my_file.MyClass.my_caller_function.requests')
      @patch('my_dir.my_file.requests')
      def test_my_caller_function(self, mock_requests):
          # when response.text is used:
          # mock_requests.post.return_value = Mock(status_code=200, ok=True, text=json.dumps({'key':'1234'}))
          # when response.json() is called:
          mock_requests.post.return_value = Mock(status_code=200, ok=True, json=Mock(return_value={'key':'1234'}))
      from mock import patch, Mock

      @patch('path.to.my_caller_function.requests')
      def test_my_caller_function(self, mock_requests):
              # define some results for the first four times the post is called:
              mock_requests.post.side_effect = [
                  Mock(status_code=200, ok=True, json=Mock(return_value={'key':'1234'})),
                  Mock(status_code=201, ok=True),
                  Mock(status_code=200, ok=True, json=Mock(return_value={'key':'1234'})),
                  Mock(status_code=201, ok=True, json=Mock(return_value={'my_field':False}))
              ]
      subprocess
      (my_dir/my_file.py)

      res = subprocess.run()
      from mock import patch, Mock

      @patch("my_dir.my_file.subprocess")
      def test_my_caller_function(self, mock_subprocess):
          mock_subprocess.run.return_value = Mock(...)

      open text file
      (my_dir/my_file.py)

      content = open("toto.txt", "r").read().strip()
      from mock import patch, mock_open

      @patch("my_dir.my_file.open")
      def test_my_caller_function(self, mock_open):
          mock_open = mock_open()
          mock_open.read.return_value = "my content"

      open json file
      (my_dir/my_file.py)

      with open("toto.json", "r") as f:
          my_dict_or_list = json.load(f)
      from mock import patch, Mock, mock_open

      @patch("my_dir.my_file.json.load")
      @patch("my_dir.my_file.open")
      def test_my_caller_function(self, mock_open, mock_json_load):
          mock_open = mock_open()
          mock_json_load.return_value = {"a": "b"}

      urlopen
      (my_dir/my_file.py)

      with urlopen(url) as my_file:
      from mock import patch, MagicMock

      @patch("my_dir_my_file.urlopen")
      def test_my_caller_function(self, mock_urlopen):
          # mock urlopen
          # Python 3 urlopen context manager mocking
          cm = MagicMock()
          cm.getcode.return_value = 200
          cm.getheaders.return_value = [
              ("Content-Type", "application/x-subrip"),
          ]
          cm.read.return_value = "contents"
          cm.__enter__.return_value = cm
          mock_urlopen.return_value = cm

      Celery task from celery import shared_task

      @shared_task(...)
      def my_function(...):
      def my_caller_function(...):
          job_result = my_function.apply_async( (...), )
      import uuid
      from mock import patch, Mock

      @patch('path.where.is.called.my_function.apply_async')
      def test_my_caller_function(..., mock_my_function_apply_async):
         # my_function is not really called
          job_uuid = uuid.uuid4()
          mock_my_function_apply_async.returned_value = Mock(id=job_uuid)
          my_caller_function(...)



      function_result = my_function(...) from mock import patch

      @patch('path.to.my_task.run')
      def test_my_caller_function(..., mock_my_function_run):
          # my_function_is_not_really_called
          mock_my_function_run.returned_value = ...
          my_caller_function(...)

      Boto3

      from mock import MagicMock, patch

      response_create_stack = {...}

      @patch('boto3.client')
      def test_my_function(self, mock_boto3_client):
          mock_create = MagicMock()
          mock_create.create_stack.return_value = response_create_stack
                 
          mock_boto3_client.return_value = mock_create

          ...
      from mock import MagicMock, patch

      response_create_stack = {...}
      response_describe_stacks = {...}

      @patch('boto3.client')
      def test_my_function(self, mock_boto3_client):
          mock_create = MagicMock()
          mock_create.create_stack.return_value = response_create_stack
          mock_update = MagicMock()
          mock_update.describe_stacks.return_value = response_describe_stacks
                 
          mock_boto3_client.side_effect = [mock_create, mock_update]

          ...
    • Nested patches:
      • @patch('path.to.first')
        @patch('path.to.second')
        def test_toto(self, mock_second, mock_first)
  • Problemes / Problems
    • @patch("my_dir.my_file.MyClass")
      ModuleNotFoundError: No module named 'my_dir.my_file.MyClass'; 'my_dir.my_file' is not a package
      • should be:
        • @patch("my_dir.my_file.my_function")
        • ...
    • "django.db.utils.InterfaceError: connection already closed"
      • Solution:
        • restart database service?
    • ...
  • ...

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
        • 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
      • Swagger
      • The browsable API
        • Disable
          • urls.py
            • urlpatterns = [
                  #url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
          • settings.py
            • REST_FRAMEWORK = {
                  'DEFAULT_RENDERER_CLASSES': (
                      'rest_framework.renderers.JSONRenderer',
                      #'rest_framework.renderers.BrowsableAPIRenderer',
                  ),
      • drf-spectacular
      • drf-yasg
        • Functional overview path params query params post params result
          section name in /docs/ Parameters Responses
          Parameters (path) Parameters (query) Parameters (body)
          generated for methods

          • POST
          • PUT
          • PATCH

          default is generated from
          • url pattern parameters
          • queryset
          • lookup_field
          • filter_backends
          • paginator
          • serializer_class
          • serializer_class
          overwritten by
          @swagger_auto_schema
          • manual_parameters
            • IN_PATH
          • query_serializer (all from a serializer)
          • manual_parameters (one by one)
            • IN_QUERY
          • request_body
          • responses
          • request_body
        • Swagger
        • Custom schema generation
          • The @swagger_auto_schema decorator
            • drf_yasg.utils.swagger_auto_schema
            • Exemples / Examples:
              • from drf_yasg.utils import swagger_auto_schema

                @swagger_auto_schema(deprecated=True)

              • from drf_yasg.utils import swagger_auto_schema

                @swagger_auto_schema(responses={400: "Something bad",500: "Something really bad"})
                @action...
                def ...
              • from drf_yasg.utils import swagger_auto_schema
                from drf_yasg import openapi

                class ...

                    test_param = openapi.Parameter('test', openapi.IN_QUERY, description="Put something here", type=openapi.TYPE_STRING)
                    @swagger_auto_schema(manual_parameters=[test_param])
                   
                @action...
              • from drf_yasg.utils import swagger_auto_schema
                class MyModelViewSet(viewsets.ModelViewSet):
                    ...
                    serializer_class = MyModelSerializer
                    input_serializer_class = MyModelInputSerializer

                    @swagger_auto_schema(request_body=MyModelInputSerializer)
                    def create(self, request, *args, **kwargs):
                        return viewsets.ModelViewSet.create(self, request, *args, **kwargs)

                    ...

                    def get_serializer_class(self):
                        # TODO: OPTIONS?
                        if self.request.method == 'GET':
                            return self.serializer_class
                        else:
                            # POST, PATCH
                            return self.input_serializer_class
          • Error responses
            • from rest_framework import status @swagger_auto_schema(
                  ...
                  responses = {
                      status.HTTP_400_BAD_REQUEST: "Something is bad",
                  }
              )
          • DRF error responses
          • Output serializer, but no input serializer:
            • from drf_yasg.utils import swagger_auto_schema, no_body

              @swagger_auto_schema(request_body=no_body)
          • Different serializer classes
          • Problemes / Problems
            • "AssertionError: Serializer class or instance required, not unicode"
              • Solució / Solution
                • make sure that you do not have "from __future__ import unicode_literals" on top of your file
                • see SwaggerAutoSchema.get_response_schemas(), where a check against str should succeed, but it fails because provided string is a unicode:
                  • if isinstance(serializer, str):
          • Support for SerializerMethodField
            • drf_yasg.utils.swagger_serializer_method
            • Exemples / Examples
              • from drf_yasg.utils import swagger_serializer_method

                class MyModeSerializer(serializers.Serializer):
                    myfield = serializers.SerializerMethodField()

                    @swagger_serializer_method(serializer_or_field=MyFieldSerializer)
                    def get_myfield(self, obj):
                        return MyFieldSerializer().data
              • from drf_yasg.utils import swagger_serializer_method

                class MyModeSerializer(serializers.Serializer):
                    myfields = serializers.SerializerMethodField()

                    @swagger_serializer_method(serializer_or_field=MyFieldSerializer(many=True))
                    def get_myfields(self, obj):
                        return MyFieldSerializer(many=True).data
          • DRF Error Responses #224
      • Django REST Swagger (deprecated)
        • 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
        • 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
          • POST entry points show parameters as a single json, instead of form fields than can be filled by user
            • Solució / Solution
              • FormParser must be the first one in DEFAULT_PARSER_CLASSES
              • settings.py
                • REST_FRAMEWORK = {
                  ...
                      'DEFAULT_PARSER_CLASSES': (
                          'rest_framework.parsers.FormParser',
                          'rest_framework.parsers.JSONParser',
                          'watchit_manager.parsers.PhpMultiPartParser',
                      ),
          • 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:
            • 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
          • djangorestframework-simplejwt
            • signing algorithm
              • signing ALGORITHM SIGNING_KEY VERIFYING_KEY
                HMAC signing (symmetric) HS256, HS384, HS512 secret string (defaults to SECRET_KEY) SIGNING_KEY
                RSA signing (asymetric) RS256, RS384, RS512 RSA private key (>2048 bits) RSA public key
            • login view (/api/token) returns two jwt tokens (signed with algorithm specified by ALGORITHM; with secret/private key specified by SIGNING_KEY (default is SECRET_KEY)):
              • refresh
                • header: {"typ":"JWT", "alg":"HS256"}
                • payload: {"token_type":"refresh", "exp":..., "iat":..., "jti":..., "user_id":...}
              • access
                • header: {"typ":"JWT", "alg":"HS256"}
                • payload: {"token_type":"access", "exp":..., "iat":..., "jti":..., "user_id":...}
            • other views, (including /api/verify) accessed with specific header:
              • -H 'Authentication Bearer my_access_jwt_token'
              • token is verified using secret/public key specified by VERIFYING_KEY
          • django-rest-framework-jwt
          • djangorestframework-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
            • DjangoFilterBackend with multiple ids
            • Logical OR on multiple values for a CharField #1076
            • options
              • BaseInFilter
                • example
                  • class CharInFilter(BaseInFilter, CharFilter):
                        pass

                    class MyModelFilterSet(FilterSet):
                        # django-filters 1.x
                        # it calls Filter.filter()
                        myfield = CharInFilter(name='myfield')

                        # it calls filter_myfields()
                        #myfield = CharInFilter(label='myfields', method='filter_myfields', help_text=_('Filter by multiple myfield'))

                        # django-filters 2.x

                        # it calls Filter.filter()
                        # parameter lookup_expr is no needed, as
                    lookup_expr='in' is the default for BaseInFilter     #myfield = CharInFilter(field_name='myfield', lookup_expr='in')

                        class Meta(object):
                            model = MyModel
              • MultipleChoiceField with disabled validation
                • Logical OR on multiple values for a CharField #1076
                • "Out of the box, the only filters that support &site=...&site=... are the multiple choice filters."
                • example
                  • import django_filters
                    from django import forms

                    class NonValidatingMultipleChoiceField(forms.MultipleChoiceField):
                        def validate(self, value):
                            pass

                    # Or inherit `filters.AllValuesMultipleFilter`
                    class NonValidatingMultipleChoiceFilter(django_filters.MultipleChoiceFilter):
                        field_class = NonValidatingMultipleChoiceField
                    class MyModelFilterSet(FilterSet):
                        # django-filters 1.x
                        # it calls MultipleChoiceFilter.filter()

                        # OR with provided values (default)
                        myfield =
                    NonValidatingMultipleChoiceFilter(name='myfield')

                        # AND with provided values (conjoined=True)
                        myfield_and =
                    NonValidatingMultipleChoiceFilter(name='myfield', conjoined=True)
              • ...
          • 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)





    • model
      serializer
      renderer
      response


      # using default renderer
      return Response(serializer.data)
      class MyModel(models.Model):
          ...
      queryset = MyModel.filter(...)
      class MyModelSerializer(serializers.ModelSerializer):
          class Meta:
              model = MyModel
              fields = ('model_field_1',)

      serializer = MyModelSerializer(queryset, many=True)
      class MyRenderer(BaseRenderer):
          def render():
              result = ...
              return result

      content = MyRenderer().render(serializer.data)
      ...
      -
      class MySimpleSerializer(serializers.Serializer):
          field_1 = serializers.CharField()
          field_2 = serializers.CharField()
          field_3 = serializers.CharField()

      obj_list = [{"field_1": "value_11",
                   "field_2": "value_12",
                   "field_3": "value_13",
                   },
                  {"field_1": "value_21",
                   "field_2": "value_22",
                   "field_3": "value_23",
                   },
      ]

      serializer = MySimpleSerializer(obj_list, many=True)

      -
      class FlatListSerializer(serializers.Serializer):
          def to_representation(self, obj):
              result = "·".join(obj)
              return result

      class MySimpleSerializer(serializers.Serializer):
          field_1 = FlatListSerializer()
          field_2 = serializers.CharField()
          field_3 = serializers.CharField()

      obj_list = [{"field_1": ["value_11a", "value_11b",],
                   "field_2": "value_12",
                   "field_3": "value_13",
                   },
                  {"field_1": ["value_21a", "value_21b",],
                   "field_2": "value_22",
                   "field_3": "value_23",
                   },
      ]

      serializer = MySimpleSerializer(obj_list, many=True)

      -
      -
      data = {'f1':'value1', 'f2':'value2'}
      content = MyRenderer().render(data)

    • 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
        @action() (new in drf 3.9)
        @detail_route() (deprecated)
        @list_route()
        (deprecated)
        def method_name(...)
        POST
        @action() (new in drf 3.9)
        @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():

                  for key, valuelist in dataandfiles.data.lists():         # 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):
                ...
      • Serialització dels queryparams / Queryparams serialization
        • Use a serializer for the query parameters
        • Good Practice to Handle Incoming Data in DRF
        • Exemples / Examples
          • ...
          • class MyQueryparamsSerializer(serializers.Serializer):
                first_field = serializers.CharField(default=
            first_field_default_value)
                second_field = serializers.IntegerField(required=False)

            class MyViewSet(viewsets.ModelViewSet)
                @swagger_auto_schema(
                    method="get", query_serializer=MyQueryparamsSerializer
                )
                @action(
                    methods=["get"]
                )
                def my_method(self, request, *args, **kwargs):
                    # get queryparams using a serializer
                    queryparams_serializer =
            MyQueryparamsSerializer(data=request.query_params)
                    if not queryparams_serializer.is_valid():
                        return Response(queryparams_serializer.errors, status.HTTP_400_BAD_REQUEST)


                    first_field_value = queryparams_serializer.validated_data("first_field")
                    second_field_value = queryparams_serializer.validated_data("second_field", second_field_default_value)
        • ...
      • Serialitzadors diferents a l'entrada (request) i a la sortida (response) / Different serializers for input (request) and output (response)
        • drf-yasg
        • 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 (use @action instead)
          • 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)
        • CreateModelMixin.create
          • self.perform_create
            • MyModelViewSet.perform_create
              • serializer.save
                • BaseSerializer.save
                  • self.create
                    • MyModelSerializer.create
                      • super
                        • TaggitSerializer.create
                          • super
                            • ModelSerializer.create
                              • ModelClass._default_manager.create(**validated_data)
                                • obj.save
                                  • MyModel.save
                                  • MyModel.post_save
                            • TaggitSerializer._save_tags
                              • _TaggableManager.set
                                • self.remove
                                • self.add
        • 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):
                  # e.g. get parent object from pk specified in url
                  ...
                  # 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(TaggitSerializer, serializers.ModelSerializer):
              # overwritten e.g. to deal with two serializations at the same time (FK)      
              def create(self, validated_data):
                  # e.g. pop from validated_data, to create related objects
                  ...
                  # 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 RetrieveModelMixin(object):
              def retrieve(self, request, *args, **kwargs):
                  instance = self.get_object()
                  serializer = self.get_serializer(instance)
                  return Response(serializer.data)


          class 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)


          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 (library_site)

          • library_site/urls.py



            from django.contrib import admin
            from django.urls import include, path

            urlpatterns = [
                # app: publications
                path("publications/", include("publications.urls")),
                # djangorestframework auth
                # drf-yasg swagger documentation
                # admin
                path("admin/", admin.site.urls),
            ]


            old:

            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('publications.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')),  
            )


            publications/models.py
            publications/urls.py
            publications/views.py
            publications/serializers.py
            from django.db import models


            class ISBN(models.Model):
                """
                ISBN code
                """

                number = models.IntegerField()


            class Publisher(models.Model):
                """
                Publisher
                """

                name = models.CharField(max_length=100)
                address = models.TextField(blank=True, null=True)


            class Author(models.Model):
                """
                Author
                """

                first_name = models.CharField(max_length=100)
                last_name = models.CharField(max_length=100)


            class Book(models.Model):
                """
                Book
                """

                isbn = models.OneToOneField(
                    ISBN,
                    blank=True,
                    null=True,
                    # when related ISBN is deleted, also delete this book
                    on_delete=models.CASCADE,
                )
                publisher = models.ForeignKey(
                    Publisher,
                    related_name="publications",
                    blank=True,
                    null=True,
                    # when related publisher is deleted, also delete this book
                    on_delete=models.CASCADE,
                )
                authors = models.ManyToManyField(Author, related_name="publications")

                title = models.CharField(max_length=200)


            class Comment(models.Model):
                book = models.ForeignKey(
                    Book,
                    related_name="comments",
                    # when related book is deleted, also delete this comment
                    on_delete=models.CASCADE,
                )

                text = models.TextField()


            from django.urls import include, path

            from rest_framework import routers

            from . import views

            app_name = "publications"

            book_router = routers.DefaultRouter()
            book_router.register(r"books", views.BookViewSet)

            urlpatterns = [
                path("", include(book_router.urls)),
            ]


            old:

            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
          • escriptura / write
          • 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',)
              • ...
          • Allow null:
          • ...
    • 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 ORM (the classical creation of database entries)
        • Funcions: get(), post(), patch(), put(), delete() must be implemented
        • must return a Response:
          • from rest_framework.response import Response
            from rest_framework.views import APIView

            class MyAPIView(APIView):
                def get(self, request, *args, **kwargs):
                    ...
                    return Response(...)
        • APIView does not have serializer_class; generics.GenericAPIVIew has it (and swagger can show it on docs)
      • GenericAPIView
        • 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
            function
            call to get_serializer
            deserialization
            CreateModelMixin
            create
            get_serializer(data=request.data)
            UpdateModelMixin update get_serializer(instance, data=request.data, partial=partial)
            serialization
            ListModelMixin
            list
            get_serializer(page, many=True)
            get_serializer(queryset, many=True)
            RetrieveModelMixin
            retrieve
            get_serializer(instance)

            DestroyModelMixin
            destroy
            -
        • Marking extra methods for routing (@action)
          • action decorator replaces list_route and detail_route (DRF 3.9)
            • @detail_route() -> @action(detail=True)
            • @list_route() -> @action(detail=False)
          • @action(...)
            • detail
              • detail=True
              • detail=False
            • methods
              • methods=["get"]
              • methods=["post"]
            • permission_classes=(permissions.AllowAny,)     
            • serializer_class=MyOutputSerializer
            • ...
          • action with extra path paramters
            • Django REST Framework - pass extra parameter to actions
            • @action(detail=True,
                      methods=['delete'],
                      url_path='contacts/(?P<phone_pk>[^/.]+)')
              def delete_phone(self, request, phone_pk, pk=None):
                  contact = self.get_object()
                  phone = get_object_or_404(contact.phone_qs, pk=phone_pk)
                  phone.delete()
                  return Response(.., status=status.HTTP_204_NO_CONTENT)

          • default method
            URL
            from rest_framework.decorators import action

            @action(detail=True,
            methods=['post'])
            def set_password(self, request, *args, **kwargs):
                my_object = self.get_object()

                ...
            GET
            ^users/{pk}/set_password/$
            # when lookup_field='toto_field':
            @action(
            detail=True, methods=['post'])
            def set_password(self, request, *args, **kwargs):
                my_object = self.get_object()
                ...

            @action(
            detail=True, methods=['post'])
            def set_password(self, request, toto_field=None):
                my_object = self.get_object()
                ...
            GET
            ^users/{toto_field}/set_password/$
            from rest_framework.decorators import action

            @action(
            detail=False)
            def recent_users(self, request, *args, **kwargs):     qs = self.get_queryset()
                qs_filtered = qs.filter(...)

                serializer = self.get_serializer(qs_filtered, many=True)
                response = Response(serializer.data)
                return response

            GET ^users/recent_users/$
          • POST example to perform an action
            • import logging

              from rest_framework import viewsets
              from rest_framework.decorators import action
              from rest_framework.response import Response
              from rest_framework.exceptions import APIException

              from my_app.models import MyModel
              from my_app.serializers import MyModelSerializer

              logger = logging.getLogger(__name__)

              class MyModelViewSet(viewsets.ModelViewSet):
                  queryset = MyModel.objects.all()
                  serializer_class = MyModelSerializer
                  lookup_field = 'name'
                 
                  @action(detail=True, methods=['post'])
                  def do_something(self, request, *args, **kwargs):
                      instance = self.get_object()
                      try:
                          instance.do_something()
                          return Response({'status': 'something done'})
                      except Exception as e:
                          logger.error("[MyModelViewSet.do_something] Exception: %s" % e)
                          raise APIException("Error when doing something: %s" % e)
          • Problemes / Problems
            • @detail_route not working with lookup_field
              • Type error: my_function() got an unexpected keyword argument ...
              • lookup_field error with detail_route
              • Solució / Solution
                • check that your function has kwargs parameter:
                  • @detail_route()
                    def my_function(self, request, *args, **kwargs):
          • deprecated
            • GET:
              • @link()
            • POST:
              • @action()
                def set_password(self, request, pk=None)
              • @action(permission_classes=[IsAdminOrIsSelf])
              • @action(methods=['POST', 'DELETE'])
    • Renderers
      • Format suffixes (to choose the render based on request suffix)
      • Examples
        • XMLRenderer
          • django-rest-framework-xml
          • XML in Python
          • myapp/renderers.py
            • """
              Provides XML rendering support.
              """
              from __future__ import unicode_literals

              from django.utils import six
              from django.utils.xmlutils import SimplerXMLGenerator
              from django.utils.six.moves import StringIO
              from django.utils.encoding import force_text
              from rest_framework.renderers import BaseRenderer

              from lxml import etree

              class XMLRenderer(BaseRenderer):
                  """
                  Renderer which serializes to XML.
                  """
                  media_type = 'application/xml'
                  format = 'xml'
                  charset = 'utf-8'
                  item_tag_name = 'list-item'
                  set_item_tag_name_from_plural = True

                  def render(self, data, accepted_media_type=None, renderer_context=None, root_tag_name='root'):
                      """
                      Renders data into serialized XML.
                      """
                      if data is None:
                          return ''
                      xml = etree.Element(root_tag_name)
                     
                      self._to_xml(xml, data)
                     
                      # convert to string
                      return etree.tostring(xml, encoding='utf-8', xml_declaration=True, pretty_print=True).decode('utf-8')
                 
                  def _to_xml(self, xml, data):
                      if isinstance(data, (list, tuple)):
                          for item in data:
                              item_tag_name = item.pop('#tag',self.item_tag_name)
                              child = etree.SubElement(xml, item_tag_name)
                              self._to_xml(child, item)
                      elif isinstance(data, dict):
                          for key, value in six.iteritems(data):
                              if key.startswith('@'):
                                  # attribute
                                  xml.set(key[1:], value)
                              elif key=='#text':
                                  # text
                                  xml.text = value
                              else:
                                  # child
                                  child = etree.SubElement(xml, key)
                                  # if value is a list, we remove 's' from key, to be used as key for each item,
                                  # provided that it is not overwritten by "#tag"
                                  if self.set_item_tag_name_from_plural:
                                      self.item_tag_name = key[:-1]
                                  self._to_xml(child, value)
                      elif data is None:
                          pass
                      else:
                          xml.text = force_text(data)
          • toto.json
            • {
                  "primer": {
                      "@attr1": "value_1",
                      "@attr2": "value_2",
                      "#text": "mytext"
                  },
                  "segon": 1,
                  "mylist": [
                      {"#tag": "myitem", "l1_a": "l1_a_value", "l1_b": "l1_b_value"},
                      {"#tag": "myotheritem","l2_a": "l2_a_value", "l2_b": "l2_b_value"}
                  ],
                  "myelements": [
                      {"el1_a": "el1_a_value", "el1_b": "el1_b_value"},
                      {"el2_a": "el2_a_value", "el2_b": "el2_b_value"}
                  ]
              }
          • test_xmlrenderer.py
            • # -*- coding: utf-8 -*-
              import django

              from django.conf import settings
              settings.configure()
              django.setup()

              import json

              from myapp.renderers import XMLRenderer

              json_path = "toto.json"
              with open(json_path, "r") as f:
                  obj = json.load(f)

              xml = XMLRenderer().render(obj, root_tag_name="myroot")

              print(xml)
          • output:
            • <?xml version='1.0' encoding='utf-8'?>
              <myroot>
                <segon>1</segon>
                <primer attr1="value_1" attr2="value_2">mytext</primer>
                <mylist>
                  <myitem>
                    <l1_a>l1_a_value</l1_a>
                    <l1_b>l1_b_value</l1_b>
                  </myitem>
                  <myotheritem>
                    <l2_a>l2_a_value</l2_a>
                    <l2_b>l2_b_value</l2_b>
                  </myotheritem>
                </mylist>
                <myelements>
                  <myelement>
                    <el1_a>el1_a_value</el1_a>
                    <el1_b>el1_b_value</el1_b>
                  </myelement>
                  <el1_>
                    <el2_b>el2_b_value</el2_b>
                    <el2_a>el2_a_value</el2_a>
                  </el1_>
                </myelements>
              </myroot>
        • XLSRenderer
        • admin action to render to a file
        • content = JSONRenderer().render(serializer.data)
      • Custom renderers
        • renderers.py
          • from rest_framework.renderers import BaseRenderer

            class UriListRenderer(BaseRenderer):
                # to be specified in Accept http header
                media_type = 'text/uri-list'
                # shown in api browser and in queryset ?format=uri-list
                format = 'uri-list'
        • views.py
          • from rest_framework.settings import DEFAULTS
            from renderers import
            UriListRenderer

            class TotoView(View):
                renderers = DEFAULTS['DEFAULT_RENDERER_CLASSES'] + (UriListRenderer,)

            class TotoViewSet(viewsets.ModelViewSet):
                # order is important: first entry determines the default renderer
                renderer_classes = (JSONRenderer, BrowsableAPIRenderer, UriListRenderer,)
        • Client
          • curl -X GET -H 'Accept: text/uri-list' ...
    • Examples from tutorial (2.3.x):

      project
      application

      test
      settings.py
      urls.py
      models.py
      serializers.py
      urls.py
      views.py

      Quickstart
      DATABASES...

      INSTALLED_APPS = (
          
      ...
          
      'rest_framework', )

      REST_FRAMEWORK
      = {
          
      'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
          
      'PAGINATE_BY': 10 }
      from django.conf.urls import patterns, url, include
      from rest_framework import routers


      router = routers.DefaultRouter()
      router.register(r'users', views.UserViewSet)

      urlpatterns = patterns('',
          url(r'^', include(router.urls)),
          url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),      
      )

      -
      from rest_framework import serializers
      from django.contrib.auth.models import User, Group

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

      class UserViewSet(viewsets.ModelViewSet):
          
      """
           API endpoint that allows users to be viewed or edited.
           """      queryset = User.objects.all()      serializer_class = UserSerializer

      curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
      Tutorial
      function based views
      (snippets)
      INSTALLED_APPS = (
           ...
           'rest_framework',
           'snippets',
      )
      urlpatterns = patterns('',
           url(r'^', include('snippets.urls')),
      )
      class Snippet(models.Model):
          created = models.DateTimeField(auto_now_add=True)
          ...
      class SnippetSerializer(serializers.ModelSerializer):
          class Meta:
              model = Snippet
              fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
      urlpatterns = patterns('snippets.views',
          url(r'^snippets/$', 'snippet_list'),
          url(r'^snippets/(?P<pk>[0-9]+)/$', 'snippet_detail'),
      )

      urlpatterns = format_suffix_patterns(urlpatterns)
      @api_view(['GET', 'POST'])
      def snippet_list(request):
      ...
      @api_view(['GET', 'PUT', 'DELETE'])
      def snippet_detail(request, pk):
      ...


      Tutorial 3: class based views


      from snippets import views

      urlpatterns = patterns('',
          url(r'^snippets/$', views.SnippetList.as_view()),
          url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
      )

      from rest_framework.views import APIView

      class SnippetList(APIView):
          def get(self, request, format=None):
          ...
          def post(self, request, format=None):
          ...

      class SnippetDetail(APIView):
          def get_object(self, pk):
          ...
          def get(self, request, pk, format=None):
          ...
          def put(self, request, pk, format=None):
          ...
          def delete(self, request, pk, format=None):
          ...



      mixins, generics


      from snippets.models import Snippet
      from snippets.serializers import SnippetSerializer
      from rest_framework import mixins
      from rest_framework import generics

      class SnippetList(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        generics.GenericAPIView):
          queryset = Snippet.objects.all()
          serializer_class = SnippetSerializer

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

          def post(self, request, *args, **kwargs):
              return self.create(request, *args, **kwargs)
         
      class SnippetDetail(mixins.RetrieveModelMixin,
                          mixins.UpdateModelMixin,
                          mixins.DestroyModelMixin,
                          generics.GenericAPIView):
          queryset = Snippet.objects.all()
          serializer_class = SnippetSerializer

          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 delete(self, request, *args, **kwargs):
              return self.destroy(request, *args, **kwargs)


      generics


      from snippets.models import Snippet
      from snippets.serializers import SnippetSerializer
      from rest_framework import generics

      class SnippetList(generics.ListCreateAPIView):
          queryset = Snippet.objects.all()
          serializer_class = SnippetSerializer

      class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
          queryset = Snippet.objects.all()
          serializer_class = SnippetSerializer


      Tutorial 4: Authentication  & Permissions


      class Snippet(models.Model):
          ...
          owner = models.ForeignKey('auth.User', related_name='snippets')
          highlighted = models.TextField()

          def save(self, *args, **kwargs):
          ...

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

      class SnippetSerializer(serializers.ModelSerializer):
          owner = serializers.Field(source='owner.username')
          class Meta:
              model = Snippet
              fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'owner')

      class UserSerializer(serializers.ModelSerializer):
          snippets = serializers.PrimaryKeyRelatedField(many=True)

          class Meta:
              model = User
              fields = ('id', 'username', 'snippets')
      urlpatterns = patterns('',
      ...
          url(r'^users/$', views.UserList.as_view()),
          url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
      ...
      )


      from django.contrib.auth.models import User
      from snippets.serializers import UserSerializer

      class SnippetList(generics.ListCreateAPIView):
          queryset = Snippet.objects.all()
          serializer_class = SnippetSerializer
          def pre_save(self, obj):
              obj.owner = self.request.user

      class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
          queryset = Snippet.objects.all()
          serializer_class = SnippetSerializer
          def pre_save(self, obj):
              obj.owner = self.request.user


      class UserList(generics.ListAPIView):
          queryset = User.objects.all()
          serializer_class = UserSerializer


      class UserDetail(generics.RetrieveAPIView):
          queryset = User.objects.all()
          serializer_class = UserSerializer


      authentication




      # login/logout at top right corner
      urlpatterns += patterns('',
          url(r'^api-auth/', include('rest_framework.urls',
                                     namespace='rest_framework')),
      )
      from rest_framework import permissions

      class SnippetList(generics.ListCreateAPIView):
          queryset = Snippet.objects.all()
          serializer_class = SnippetSerializer
          permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
      ...
      class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
          queryset = Snippet.objects.all()
          serializer_class = SnippetSerializer
          permission_classes = (permissions.IsAuthenticatedOrReadOnly,)


      permissions





      from snippets.permissions import IsOwnerOrReadOnly

      class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
          queryset = Snippet.objects.all()
          serializer_class = SnippetSerializer
          permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
      ...
      from rest_framework import permissions

      class IsOwnerOrReadOnly(permissions.BasePermission):
          def has_object_permission(self, request, view, obj):
              # Read permissions are allowed to any request,
              # so we'll always allow GET, HEAD or OPTIONS requests.
              if request.method in permissions.SAFE_METHODS:           
                  return True

              # Write permissions are only allowed to the owner of the snippet
              return obj.owner == request.user

      Tutorial 5: Relationships & Hyperlinked APIs




      urlpatterns = format_suffix_patterns(patterns('snippets.views',
          url(r'^$', 'api_root'),
          url(r'^snippets/$',
              views.SnippetList.as_view(),
              name='snippet-list'),
          url(r'^snippets/(?P<pk>[0-9]+)/$',
              views.SnippetDetail.as_view(),
              name='snippet-detail'),
          url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
              views.SnippetHighlight.as_view(),
              name='snippet-highlight'),
          url(r'^users/$',
              views.UserList.as_view(),
              name='user-list'),
          url(r'^users/(?P<pk>[0-9]+)/$',
              views.UserDetail.as_view(),
              name='user-detail')
      ))

      from rest_framework import renderers
      from rest_framework.decorators import api_view
      from rest_framework.response import Response
      from rest_framework.reverse import reverse

      @api_view(('GET',))
      def api_root(request, format=None):
          return Response({
              'users': reverse('user-list', request=request, format=format),
              'snippets': reverse('snippet-list', request=request, format=format)
          })


      highlighted




      ...
          url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
      from rest_framework import generics

      class SnippetHighlight(generics.GenericAPIView):
          queryset = Snippet.objects.all()
          renderer_classes = (renderers.StaticHTMLRenderer,)

          def get(self, request, *args, **kwargs):
              snippet = self.get_object()
              return Response(snippet.highlighted)   



      hyperlinked



      class SnippetSerializer(serializers.HyperlinkedModelSerializer):
          owner = serializers.Field(source='owner.username')
          highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')

          class Meta:
              model = Snippet
              fields = ('url', 'highlight', 'owner',
                        'title', 'code', 'linenos', 'language', 'style')
             
             
      class UserSerializer(serializers.HyperlinkedModelSerializer):
          snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail')

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




      Tutorial 6: ViewSets & Routers




      from django.conf.urls import patterns, url, include
      from snippets import views
      from rest_framework.routers import DefaultRouter

      # Create a router and register our viewsets with it.
      router = DefaultRouter()
      router.register(r'snippets', views.SnippetViewSet)
      router.register(r'users', views.UserViewSet)

      # The API URLs are now determined automatically by the router.
      # Additionally, we include the login URLs for the browseable API.
      urlpatterns = patterns('',
          url(r'^', include(router.urls)),
          url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
      )
      from rest_framework import viewsets
      from rest_framework.decorators import link

      class UserViewSet(viewsets.ReadOnlyModelViewSet):
          """
          This viewset automatically provides `list` and `detail` actions.
          """
          queryset = User.objects.all()
          serializer_class = UserSerializer


      class SnippetViewSet(viewsets.ModelViewSet):
          """
          This viewset automatically provides `list`, `create`, `retrieve`,
          `update` and `destroy` actions.

          Additionally we also provide an extra `highlight` action.
          """
          queryset = Snippet.objects.all()
          serializer_class = SnippetSerializer
          permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                                IsOwnerOrReadOnly,)

          @link(renderer_classes=[renderers.StaticHTMLRenderer])
          def highlight(self, request, *args, **kwargs):
              snippet = self.get_object()
              return Response(snippet.highlighted)

          def pre_save(self, obj):
              obj.owner = self.request.user



      settings.py
      urls.py models.py serializers.py urls.py views.py permissions.py
      test

      project
      application
    • Example: override the save method to add an action before it (Django signals vs. custom save()-method) (Django tips: auto-populated fields) (Django signals)
      • 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: update DNI on an extended user identified by request (instead of pk):
      • models.py:
        • class ModifiedUser(AbstractUser):
              dni = models.CharField(max_length=9)
      • serializers.py
        • class ModifiedUserUpdateSerializer(serializers.ModelSerializer):
              class Meta:
                  model = ModifiedUser
                  fields = ('dni',)
      • views.py
        • class ModifiedUserUpdateAPIView(generics.UpdateAPIView):
              """
              Update the user, according to the authenticated credentials.
              """
              queryset = ModifiedUser.objects.all()
              serializer_class = ModifiedUserUpdateSerializer
              permission_classes = (permissions.IsAuthenticated,)

              # as modifieduser pk is not explicitly provided,
              # get it from the user on the request   
              def get_object(self):
                  p = self.request.user
                  try:
                      return ModifiedUser.objects.get(pk=p.id)
                  except Participant.DoesNotExist:
                      raise Http404
        • # define a replacement for api_root, as the DefaultRouter (which generates api_root)
          # cannot register ViewSet entries without "-list" in the URL name (in this case, name,
          # specified in urls.py and referenced here as reverse, contains "-detail")
          @api_view(('GET',))
          def api_arrel(request, format=None):
              return Response({
                   ...
                   'update_modifieduser': reverse('updatemodifieduser-detail', request=request, format=format),
              })
      • urls.py
        • urlpatterns = patterns('',
              url(r'^update_modifieduser/$', views.ModifiedUserUpdateAPIView.as_view(), name='updatemodifieduser-detail' ),
              url(r'^api_arrel/$', 'my_app.views.api_arrel'),
          )
    • Example: action on a queryset
      • see also admin
      • ...
    • Example: action on a single object:
      • see also highlight
    • Example: toto1/{toto2} (instead of default toto1/{pk}); returns only entries owned by user that performs the query (curl -u usuari:contrasenya)
      • models.py
        • class Toto(models.Model):
              user = models.ForeignKey(User)
              toto2 = models.ForeignKey(Toto2)
              ...
      • views.py
        • class TotoViewSet(viewsets.ModelViewSet):
              queryset = Toto.objects.all()
              serializer_class = TotoSerializer
              permission_classes = (permissions.IsAuthenticated,)
              lookup_field = 'toto2'
             
              # when creating an entry, automatically put the user on the object, according to the request
              def pre_save(self, obj):
                  obj.user = self.request.user
             
              # only entries that belong to user
              def get_queryset(self):
                  user = self.request.user
                  return user.userchallenge_set.all()
      • urls.py
        • router.register(r'userchallenges', views.UserChallengeViewSet)
    • Example: creation of a user with a given password
      • views.py
        • class CreateViewSet(mixins.CreateModelMixin,
                              viewsets.GenericViewSet):
              pass

          class UserCreateViewSet(CreateViewSet):
              queryset = User.objects.all()
              serializer_class = UserSerializer

              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)
                      #headers = self.get_success_headers(serializer.data)
                      #return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
                      return Response(serializer.data, status=status.HTTP_201_CREATED)
                  return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      • urls.py
        • router.register(r'new_user', views.UserCreateViewSet)
    • Examples (2.2.x):

      models.py
      resources.py
      forms.py
      views.py
      urls.py
      Sandbox Root API -
      -
      -
      class Sandbox(View):
       
      def get(self, request):
      -
      Blog Posts API
      class BlogPost(models.Model):
      class Comment(models.Model):
      class BlogPostResource(ModelResource):
        model = BlogPost

      class
      CommentResource(ModelResource):
        model = Comment
      -
      -
      ListOrCreateModelView.as_view(resource=BlogPostResource)
      InstanceModelView.as_view(resource=BlogPostResource) ListOrCreateModelView.as_view(resource=CommentResource)
      InstanceModelView.as_view(resource=CommentResource)
      Getting Started - Model Views class MyModel(models.Model): class MyModelResource(ModelResource):
        model = MyModel
      -
      -
      ListOrCreateModelView.as_view(resource=MyModelResource)
      InstanceModelView.as_view(resource=MyModelResource)
      Object Store API
      -
      -
      -
      class ObjectStoreRoot(View):
        def get(self, request):
        def post(self, request):

      class
      StoredObject(View):
        def get(self, request):
        def put(self, request):
        def delete(self, request):
      ObjectStoreRoot.as_view()
      StoredObject.as_view()
      Code Highlighting API
      -
      -
      class PygmentsForm(forms.Form): class
      HTMLRenderer(BaseRenderer):
        media_type = 'text/html'

      class
      PygmentsRoot(View):
        form = PygmentsForm
        def get(self, request):
        def post(self, request):

      class PygmentsInstance(View):
        renderers = (HTMLRenderer,)
        def get(self, request, unique_id):
        def delete(self, request,unique_id):utilitats
      PygmentsRoot.as_view()
      PygmentsInstance.as_view()
      Getting Started - VIews
      -
      -
      class MyForm(forms.Form): class ExampleView(View):
        def get(self, request):

      class AnotherExampleView(View):
        form = MyForm
        def get(self, request, num):
        def post(self, request, um):
      ExampleView.as_view()
      AnotherExampleView.as_view()
      Using Django REST framework Mixin classes


      from djangorestframework.renderers import DEFAULT_RENDERERS
      from rest_framework.settings import DEFAULTS

      class
      ExampleView(ResponseMixin, View):
        renderers = DEFAULT_RENDERERS
        renderers = DEFAULTS['DEFAULT_RENDERER_CLASSES']

        def get(self, request):
           url = reverse('mixin-view', request=request)
           response = Response(200, {'description': 'Some example content',
      'url': url})
           return self.render(response)


      models.py
      resources.py forms.py views.py urls.py
    • [djcode/mysite/mysite/]settings.py
      • INSTALLED_APPS = (
        ...,
        'djangorestframework'
        )
    • Example "Getting started - Views"
      • cd mysite
      • python manage.py startapp resourceexample
      • settings.py
        • INSTALLED_APPS = (
          ...
          'resourceexample',
          )
      • urls.py
        • ...
          # rest
          from resourceexample.views import ExampleView, AnotherExampleView

          urlpatterns = patterns('',
              url(r'^$',ExampleView.as_view(), name='example-resource'),
              url(r'^(?P<num>[0-9]+)/$', AnotherExampleView.as_view(), name='another-example'),
              ...
          )
      • cd resourceexample
        • forms.py
          • 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.')
        • views.py
          • from django.core.urlresolvers import reverse

            from
            rest_framework.views import View
            from
            rest_framework.response import Response
            from rest_framework import status

            from resourceexample.forms import MyForm


            class ExampleView(View):
                """
                A basic read-only view that points to 3 other views.
                """

                def get(self, request):
                    """
                    Handle GET requests, returning a list of URLs pointing to 3 other views.
                    """
                    return {"Some other resources": [reverse('another-example', kwargs={'num':num}) for num in range(3)]}


            class AnotherExampleView(View):
                """
                A basic view, that can be handle GET and POST requests.
                Applies some simple form validation on POST requests.
                """
                form = MyForm

                def get(self, request, num):
                    """
                    Handle GET requests.
                    Returns a simple string indicating which view the GET request was for.
                    """
                    if int(num) > 2:
                        return Response(status.HTTP_404_NOT_FOUND)
                    return "GET request to AnotherExampleResource %s" % num

                def post(self, request, num):
                    """
                    Handle POST requests, with form validation.
                    Returns a simple string indicating what content was supplied.
                    """
                    if int(num) > 2:
                        return Response(status.HTTP_404_NOT_FOUND)
                    return "POST request to AnotherExampleResource %s, with content: %s" % (num, repr(self.CONTENT))
      • Test:
        • http://127.0.0.1:8000/
        • with Firefox Poster plug-in
        • with curl from command line:
          • curl -X GET http://127.0.0.1:8000/ -H 'Accept: text/plain'
          • curl -X POST --data 'foo=true' http://127.0.0.1:8000/0/
          • curl -X POST -H 'Content-Type: application/json' --data-binary '{"foo":true}' http://127.0.0.1:8000/0/
    • Other examples

        •   ...
    • ...
  • Tastypie
    • Documentation
    • Info:
    • # easy-install django-tastypie
    • django-admin.py startproject project_name
    • [djcode/project_name/]settings.py
      • INSTALLED_APPS = (
        ...,
        'tastypie'
        )
    • Example (ModelResource)
      • cd project_name
      • python manage.py startapp llibres
      • settings.py
        • INSTALLED_APPS = (
          ...,
          'llibres'
          )
      • urls.py
        • from django.conf.urls.defaults import patterns, include, url

          urlpatterns = patterns('',
              (r'', include('llibres.urls')),
              ...
          )
      • cd llibres
        • models.py
          • from django.db import models

            class Llibre(models.Model):
                titol = models.CharField(max_length=30)
                nombre_pagines = models.IntegerField()
        • api.py
          • from tastypie.resources import ModelResource from tastypie.authentication import BasicAuthentication from tastypie.authorization import Authorization #from tastypie.authorization import DjangoAuthorization
            from llibres.models import Llibre

            class LlibreResource(ModelResource):
                class Meta:
                    queryset = Llibre.objects.all()
                    resource_name = 'recurs_llibre'
                    authentication = BasicAuthentication()
                    authorization = Authorization()
                    # to use authorization from permissions set by Django admin:
                    #authorization = DjangoAuthorization()
        • urls.py
          • from django.conf.urls.defaults import patterns, include, url
            from api import LlibreResource

            llibre_resource = LlibreResource()

            urlpatterns = patterns('',
                (r'^api/', include(llibre_resource.urls)),
                ...
            )
      • Test:
        • http://127.0.0.1:8000/api/recurs_llibre/
        • get all the objects:
          • curl "http://127.0.0.1:8000/api/recurs_llibre/"
        • get the schema:
          • curl "http://127.0.0.1:8000/api/recurs_llibre/schema/?format=json"
        • add an entry:
          • curl -i -H "Content-Type: application/json" -X POST --data '{"titol": "ei", "nombre_pagines": 2}' http://127.0.0.1:8000/api/recurs_llibre/
        • query one specific entry:
          • from command line:
            • curl "http://127.0.0.1:8000/api/recurs_llibre/1/"
          • from browser:
            • http://127.0.0.1:8000/api/recurs_llibre/1/?format=json
        • add another entry:
          • curl -i -H "Content-Type: application/json" -X POST --data '{"titol": "ei2", "nombre_pagines": 4}' http://127.0.0.1:8000/api/recurs_llibre/
        • delete the first entry (will return "HTTP/1.0 204 NO CONTENT"):
          • curl -i -X DELETE http://127.0.0.1:8000/api/recurs_llibre/1/
        • use user authentication:
          • curl --user usuari:contrasenya ...
    • Example (ModelResource) with 2 resources, validation:
      • cd llibres
        • api.py
          • from tastypie.resources import ModelResource
            from tastypie.authorization import Authorization
            from llibres.models import Llibre

            from django.contrib.auth.models import User
            from tastypie.validation import Validation

            class LlibreResource(ModelResource):
                class Meta:
                    queryset = Llibre.objects.all()
                    resource_name = 'recurs_llibre'
                    authorization = Authorization()
                   
            class UserResource(ModelResource):
                class Meta:
                    queryset = User.objects.all()
                    resource_name = 'auth/user'
                    excludes = ['email', 'password', 'is_superuser']
                    # Add it here.
                    validation = Validation()
        • urls.py
          • from django.conf.urls.defaults import patterns, include, url
            from tastypie.api import Api
            from api import LlibreResource, UserResource

            v1_api = Api(api_name='v1')
            v1_api.register(LlibreResource())
            v1_api.register(UserResource())

            urlpatterns = patterns('',
                (r'^api/', include(v1_api.urls)),
            )
      • Test:
        • curl -i "http://127.0.0.1:8000/api/v1/recurs_llibre/schema/?format=json"
        • ...
        • curl -i "http://127.0.0.1:8000/api/v1/auth/user/?format=json"
    • Example (Resource) (Using Tastypie With Non-ORM Data Sources)

Loggers

  • Logging (dev)
    • /usr/lib64/python2.7/logging/handlers.py
    • (Semi-)correct handling of log rotation in multiprocess Python applications
      • when using several workers, use system logrotate instead of Django RotatingFileHandler
    • Exemple / Example:
      • my_project/settings.py
        • LOGGING = {
              'version': 1,
              'disable_existing_loggers': False,
              'formatters': {
                  'estandard': {
                      'format' : "[%(asctime)s.%(msecs)03d] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
                      'datefmt' : "%d/%b/%Y %H:%M:%S"
                  },
              },
              'handlers': {
                  'consola':{
                      'level':'DEBUG',
                      'class':'logging.StreamHandler',
                      'formatter': 'estandard'
                  },
                  'fitxer': {
                      'level': 'DEBUG',
                      'class': 'logging.FileHandler',
                      'filename':  os.path.join(BASE_DIR, 'debug.log'),
                      'formatter': 'estandard',
                  },
                  'fitxer_rotacio': {
                      'level': 'DEBUG',
                      'class':'logging.handlers.RotatingFileHandler',
                      'filename':  os.path.join(BASE_DIR, 'debug_rotacio.log'),
                      # to add numerical external IP address to filename:
                      # import socket
                      #
          'filename':  os.path.join(BASE_DIR, 'debug_rotacio.log' % socket.gethostbyname_ex(socket.gethostname())[2][0]),
                      'formatter': 'estandard',
                      'maxBytes': 50000,
                      'backupCount': 20,
                  },

              },
              'loggers': {
                  'my_app': {
                      'handlers': ['consola','fitxer'],
                      'level': 'DEBUG',
                      'propagate': True,
                  },
                  'my_second_app': {
                      'handlers': ['consola','fitxer_rotacio'],
                      'level': 'DEBUG',
                      'propagate': True,
                  },

              },
          }
      • my_app/my_file.py
        • import logging
          logger = logging.getLogger(__name__)

          ...
              logger.debug("My message with param {}", myparam) # new syntax

              logger.info("My message")
              logger.warning("My message")
              logger.error("My message")
              logger.exception("My message")

              logger.critical("My message")
    • Pylint
    • Problemes / Problems
      • Rotació i permisos / Rotation and permissions
        • Django log rotating and log file ownership
        • Solució / Solution: GroupWriteRotatingFileHandler
          • Does python logging.handlers.RotatingFileHandler allow creation of a group writable log file?
          • ScraperWikiX
          • create the log directory with the right permissions:
            • mkdir /var/log/django/
            • chown django.django /var/log/django/
            • chmod 2775 /var/log/django/ (chmod g+ws /var/log/django/)
          • create local_logging package
            • manually:
              • cd myproject
              • mkdir local_logging
              • touch local_logging/__init__.py
            • Eclipse:
              • (right-click over my_project) New -> PyDevPackage: local_logging
          • my_project/local_logging/custom_handlers.py
            • import logging.handlers
              import os
               
              class GroupWriteRotatingFileHandler(logging.handlers.RotatingFileHandler):   
                  """
                  Create a rotating log file with group write permissions
                  (so as it can be modified by regular user (who calls manage.py) and user that executes celery or uwsgi)
                  """ 
                  def _open(self):
                      prevumask=os.umask(0o002)
                      rtv=logging.handlers.RotatingFileHandler._open(self)
                      os.umask(prevumask)
                      return rtv
          • my_project/settings.py
            • import logging
              from local_logging import custom_handlers
              logging.custom_handlers = custom_handlers

              'logfile': {
                          ...
                          'class':'logging.custom_handlers.GroupWriteRotatingFileHandler',
                          'filename': "/var/log/django/myproject.log",
                          'maxBytes': 50000,
                          'backupCount': 20,
                          ...
  • Simple Log to File example for django 1.3+

Activitat i notificacions / Activity and notifications

Correu electrònic / Email

  • Sending email



  • error 500 when DEBUG=False
    mail_admins()
    send_mail()
    others (allauth, ...)
    from

    SERVER_EMAIL from_email=... DEFAULT_FROM_EMAIL
    to

    ADMINS recipient_list=[...] ...
    backend


    EMAIL_BACKEND
    SMTP backend host EMAIL_HOST
    port EMAIL_PORT
    user EMAIL_HOST_USER
    password EMAIL_HOST_PASSWORD
    from
    DEFAULT_FROM_EMAIL
    security
    EMAIL_USE_TLS
    EMAIL_USE_SSL

    client certs
    EMAIL_SSL_CERTFILE EMAIL_SSL_KEYFILE
  • Steps
    1. Install one email server:
    2. settings.py (defaults specified in lib/python2.7/site-packages/django/conf/global_settings.py)
      • EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
        EMAIL_HOST = 'localhost'
        EMAIL_PORT = 25
        EMAIL_HOST_USER = ''
        EMAIL_HOST_PASSWORD = ''
        EMAIL_USE_TLS = False
        EMAIL_USE_SSL = False
        EMAIL_SSL_CERTFILE = None
        EMAIL_SSL_KEYFILE = None
        EMAIL_TIMEOUT = None
        DEFAULT_FROM_EMAIL = 'Whatever <whatever@example.com>'
      • # for debugging purposes:
        EMAIL_BACKEND='django.core.mail.backends.console.EmailBackend'
      • EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
        EMAIL_HOST = 'smtp.example.org'
        EMAIL_PORT = 587
        EMAIL_HOST_USER = 'my_smtp_user'
        EMAIL_HOST_PASSWORD = 'my_smtp_password'
        EMAIL_USE_TLS = True
        DEFAULT_FROM_EMAIL = 'Whatever <whatever@example.org>'
  • Test
    • Testing tools
    • settings.py
      • # from address used in mail_admins() and error reporting for production
        SERVER_EMAIL='user@example.org'
    • ./manage.py shell
      • from django.core.mail import mail_admins
        mail_admins('hello', 'hello world')
  • SMTP services
  • Verificació d'una adreça de correu electrònic / Email address verification

Push notifications

Slack

Tasques / Tasks

  • Using channels (websockets)
  • Celery (asynchronous processing)
    • First steps with Celery
    • Índex / Index
    • Using Celery with Django (example)
      • General (project-wide) settings
        • myproject/myproject/celery.py
          • from __future__ import absolute_import
            import os
            from celery import Celery
            from django.conf import settings

            # set the default Django settings module for the 'celery' program.
            os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

            app = Celery('myproject')

            # Using a string here means the worker will not have to
            # pickle the object when using Windows.
            app.config_from_object('django.conf:settings')
            app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

            @app.task(bind=True)
            def debug_task(self):
                print('Request: {0!r}'.format(self.request))
        • myproject/myproject/__init__.py
          • from __future__ import absolute_import

            # This will make sure the app is always imported when
            # Django starts so that shared_task will use this app.
            from .celery import app as celery_app
        • myproject/myproject/settings.py
          • # celery
            BROKER_URL = 'amqp://myuser:mypassword@localhost/
            myvhost'
      • django-celery
      • Run the worker process
        • command line (for development)
          • worker
            • celery -A myproject worker -l info
          • worker + scheduler (periodic task) (beat)
            • celery -A myproject worker -B -l info
        • daemon (for production)
          • Running the worker as a daemon
          • Daemonization
          • service

            type
            file
            config file
            usage
            Init script: celeryd init.d /etc/init.d/celeryd
            /etc/default/celeryd
            /etc/init.d/celeryd {start|stop|restart|status}
            Init script: celerybeat /etc/init.d/celerybeat
            /etc/default/celerybeat
            /etc/default/celeryd

            /etc/init.d/celerybeat {start|stop|restart}
            Service file: celery.service systemd /usr/lib/systemd/system/celery.service
            (see celery.service for CentOS)
            /etc/conf.d/celery
            (see celery.conf for CentOS)
            systemctl {start|stop|restart|status} celery.service
            /usr/lib/systemd/system/celery.service
            (see celerybeat.service for CentOS)
            systemctl {start|stop|restart|status} celerybeat.service
          • Example (including celerybeat)
            • CentOS
              • Examples
              • Troubleshooting
                • systemd issues
                • celery issues
                • Celery Daemon does not work on Centos 7
                • executables (ExecStart*) specified in celery.service must be specified directly, not as variables from EnvironmentFile
                • user and group (User, Group) specified in celery.service must be specified directly, not as variables from EnvironmentFile
                • when using virtualenv, executable is (instead of just "celery", or "/home/centos/myproject/env/bin/celery"):
                  • /home/centos/myproject/env/bin/python -m celery
                • user and group specified in celery.service (User=django, Group=django) must have access to:
                  • /var/{log,run}/celery/...
                    • included in celery[beat].service:
                      • mkdir -p /var/log/celery; chown django.django /var/log/celery
                      • mkdir -p /var/run/celery; chown django.django /var/run/celery
                    • (?) celery.conf
                      • d /var/run/celery 0755 celery celery -
                        d /var/log/celery 0755 celery celery -
                  • WorkingDirectory=/home/centos/myproject/
                    • only if Django logger specify a log file inside this directory
                    • but my recommendation for log file is: /var/log/django/myproject.log
                • check
                  • manually start and stop of worker(s):
                    • su django
                    • /home/centos/myproject/env/bin/python -m celery worker -A myproject --workdir=/home/centos/myproject
                      • should show console output (in yellow) with prints when calling task functions (tasks.py) from another console:
                        • cd /home/centos/myproject/
                        • source env/bin/activate
                        • ./manage.py shell
                          • from myproject.tasks import *
                            do_something.delay()
                      • if no output is shown at first console:
                        • check rabbitmq queues for virtual host celery_vhost:
                          • $ ./rabbitmqadmin -V celery_vhost -u celery_user -p <password> list queues
                          • # rabbitmbctl -p celery_vhost list_queues
                          • # rabbitmqctl -n rabbit@ip-172-31-20-115 -p celery_vhost list_queues
                          • if celery queue is not empty, purge it:
                            • $ ./rabbitmqadmin -V celery_vhost -u celery_user -p <password> purge queue name=celery
                    • /home/centos/myproject/env/bin/python -m celery multi start w1 --verbose -A myproject --workdir=/home/centos/myproject --pidfile=/var/run/celery/%N.pid --logfile=/var/log/celery/%N.log --loglevel="DEBUG"
                      • ls -l /var/run/celery/w1.pid
                        • should show a valid file (with a pid number inside)
                      • tail -f /var/log/celery/w1.log
                        • should show prints from called functions (tasks.py)
                    • /home/centos/myproject/env/bin/python -m celery multi stopwait w1 --verbose --pidfile=/var/run/celery/%N.pid
                      • should show:
                        • celery multi v3.1.19 (Cipater)
                          > Stopping nodes...
                                  > w1@...: TERM -> 15263
                          > Waiting for 1 node -> 15263.....
                                  > w1@...: OK
                  • start service
                    • systemctl start celery.service
                    • systemctl status celery.service
                      • should show:
                        • Active: active (running)
                      • tail -f /var/log/celery/w1.log
                        • should show prints from called functions (tasks.py)
                      • if status is showing:
                        • Active: inactive (dead)
                        • try again to start
                    • journalctl -u celery
                    • more /var/run/celery/*
                    • more /var/log/celery/*
                  • rabbitmq is running and permissions are ok (BROKER_URL)
              • celery.conf (/etc/sysconfig/celery)
                • # this file must be installed as /etc/sysconfig/celery

                  ## common worker and beat settings

                  # Absolute or relative path to the 'celery' command:
                  #CELERY_BIN="/home/centos/my_project/env/bin/python -m celery"

                  # App instance to use
                  # comment out this line if you don't use an app.
                  # directory name where celery.py resides
                  CELERY_APP="my_app"

                  # Workers should run as an unprivileged user.
                  #   You need to create this user manually (or you can choose
                  #   a user/group combination that already exists, e.g. nobody).
                  #CELERYD_USER="celery"
                  #CELERYD_GROUP="celery"

                  ## Worker settings
                  CELERYD_NODES="w1"
                  CELERYD_OPTS="--time-limit=300 --concurrency=8"
                  CELERYD_LOG_DIR="/var/log/celery"
                  CELERYD_LOG_FILE="/var/log/celery/%N.log"
                  CELERYD_LOG_LEVEL="INFO"
                  CELERYD_PID_DIR="/var/run/celery"
                  CELERYD_PID_FILE="/var/run/celery/%N.pid"
                  CELERYD_CHDIR="/home/centos/my_project/"

                  ## Beat settings
                  CELERYBEAT_LOG_DIR="/var/log/celery"
                  CELERYBEAT_LOG_FILE="/var/log/celery/beat.log"
                  CELERYBEAT_LOG_LEVEL="INFO"
                  CELERYBEAT_PID_DIR="/var/run/celery"
                  CELERYBEAT_PID_FILE="/var/run/celery/beat.pid"
                  CELERYBEAT_SCHEDULE="/var/run/celery/celerybeat-schedule"
                  CELERYBEAT_CHDIR="/home/centos/my_project/"

                  DJANGO_SETTINGS_MODULE="myproject.settings"

                  # AWS boto credentials
                  # needed when instance has no IAM role
                  # defining them in myproject.settings is not enough
                  #AWS_ACCESS_KEY_ID = '...'
                  #AWS_SECRET_ACCESS_KEY = '...'
              • celery.service (/usr/lib/systemd/system/celery.service)
                • [Unit]
                  Description=Celery workers
                  After=network.target redis.target

                  [Service]
                  Type=forking

                  # same value as ${CELERYD_PID_FILE}, but must be specified.
                  # needed in order to avoid some immediate stops of the service
                  PIDFile=
                  /var/run/celery/w1.pid

                  # User and Group cannot be specified at EnvironmentFile
                  User=django
                  Group=django

                  EnvironmentFile=-/etc/sysconfig/celery

                  # run ExecStartPre as priviledged user and set up /var/run
                  PermissionsStartOnly=true
                  #ExecStartPre=/usr/sbin/useradd celery
                  ExecStartPre=-/usr/bin/mkdir -p ${CELERYD_PID_DIR}
                  ExecStartPre=/usr/bin/chown -R django:django ${CELERYD_PID_DIR}
                  ExecStartPre=-/usr/bin/mkdir -p ${CELERYD_LOG_DIR}
                  ExecStartPre=/usr/bin/chown -R django:django ${CELERYD_LOG_DIR}

                  # executable cannot be specified at EnvironmentFile
                  ExecStart=/home/centos/myproject/env/bin/python -m celery  \

                      --workdir=${CELERYD_CHDIR} \     -A ${CELERY_APP} \
                     
                  multi start ${CELERYD_NODES} --verbose \     --pidfile=${CELERYD_PID_FILE} \
                      --logfile=${CELERYD_LOG_FILE} \
                      --loglevel="${CELERYD_LOG_LEVEL}" \
                      ${CELERYD_OPTS}
                  ExecStop=/home/centos/myproject/env/bin/python -m celery multi stopwait ${CELERYD_NODES} \
                      --pidfile=${CELERYD_PID_FILE}
                  ExecReload=/home/centos/myproject/env/bin/python -m celery multi restart ${CELERYD_NODES} \
                      -A ${CELERY_APP} \
                      --workdir=${CELERYD_CHDIR} \
                      --pidfile=${CELERYD_PID_FILE} \
                      --logfile=${CELERYD_LOG_FILE} \
                      --loglevel="${CELERYD_LOG_LEVEL}" \
                      ${CELERYD_OPTS}

                  [Install]
                  WantedBy=multi-user.target
              • celerybeat.service (/usr/lib/systemd/system/celerybeat.service)
                • [Unit]
                  Description=Celery beat scheduler
                  After=network.target redis.target

                  [Service]
                  Type=simple
                  User=django
                  Group=django
                  EnvironmentFile=-/etc/sysconfig/celery
                  #WorkingDirectory=$CELERYD_CHDIR

                  # run ExecStartPre as priviledged user and set up /var/run
                  PermissionsStartOnly=true
                  #ExecStartPre=/usr/sbin/useradd celery
                  ExecStartPre=-/usr/bin/mkdir -p ${CELERYBEAT_PID_DIR}
                  ExecStartPre=/usr/bin/chown -R django:django ${CELERYBEAT_PID_DIR}
                  ExecStartPre=-/usr/bin/mkdir -p ${CELERYBEAT_LOG_DIR}
                  ExecStartPre=/usr/bin/chown -R django:django ${CELERYBEAT_LOG_DIR}

                  ExecStart=/home/centos/myproject/env/bin/python -m celery beat \
                      -A ${CELERY_APP} \
                      --workdir=${CELERYBEAT_CHDIR} \
                      --pidfile=${CELERYBEAT_PID_FILE} \
                      --logfile=${CELERYBEAT_LOG_FILE} \
                      --loglevel=${CELERYBEAT_LOG_LEVEL} \
                      --schedule=${CELERYBEAT_SCHEDULE}
                  ExecStop=/bin/systemctl kill celerybeat.service

                  [Install]
                  WantedBy=multi-user.target
              • celery.logrotate
                • /var/log/celery/*log {
                      daily
                      rotate 10
                      size 5M
                  }
              • install_celery.sh
                • #!/bin/bash

                  if [[ $EUID -ne 0 ]]; then
                    echo "This script must be run as root" 1>&2
                    exit 1
                  fi

                  # create user celery if it does not exist
                  if ! id -u django > /dev/null 2>&1; then
                      useradd django
                      echo "Created user django"
                  else
                      echo "User django already exists"
                  fi

                  echo "Copying files..."
                  install celery.conf /etc/sysconfig/celery
                  install celerybeat.service /usr/lib/systemd/system/celerybeat.service
                  install celery.service /usr/lib/systemd/system/celery.service
              • fabfile.py (alternative to install_celery.sh)
                • def celery_setup():
                      """
                      Setup Celery (worker)
                      """
                      print '-------------- celery_setup --------------'

                      put('celery.conf', '/etc/sysconfig/celery', use_sudo=True)
                      put('celery.service', '/usr/lib/systemd/system/celery.service', use_sudo=True)
                      put('celery.logrotate', '/etc/logrotate.d/celery', mode=int('644', 8), use_sudo=True)
                      env.sudo('chown root.root /etc/logrotate.d/celery')

                      env.sudo('systemctl enable celery.service')
                      env.sudo('systemctl start celery.service')

                  def celerybeat_setup():
                      """
                      Setup Celery (scheduler/beat)
                      """
                      print '-------------- celerybeat_setup --------------'

                      put('celerybeat.service', '/usr/lib/systemd/system/celerybeat.service', use_sudo=True)

                      env.sudo('systemctl enable celerybeat.service')
                      env.sudo('systemctl start celerybeat.service')
            • Ubuntu
              • celeryd.conf (/etc/default/celeryd)
                • # Names of nodes to start
                  #   most will only start one node:
                  CELERYD_NODES="worker1"
                  #   but you can also start multiple and configure settings
                  #   for each in CELERYD_OPTS (see `celery multi --help` for examples).
                  #CELERYD_NODES="worker1 worker2 worker3"

                  # Absolute or relative path to the 'celery' command:
                  #CELERY_BIN="/usr/local/bin/celery"
                  #CELERY_BIN="/virtualenvs/def/bin/celery"
                  CELERY_BIN="/opt/p27/bin/celery"

                  # App instance to use
                  # comment out this line if you don't use an app.
                  # (directory name where celery.py resides)
                  CELERY_APP="myproject"
                  # or fully qualified:
                  #CELERY_APP="proj.tasks:app"

                  # Where to chdir at start.
                  CELERYD_CHDIR="/home/ubuntu/my_project/"

                  # Extra command-line arguments to the worker
                  CELERYD_OPTS="--time-limit=300 --concurrency=8"

                  # %N will be replaced with the first part of the nodename.
                  CELERYD_LOG_FILE="/var/log/celery/%N.log"
                  CELERYD_PID_FILE="/var/run/celery/%N.pid"

                  # Workers should run as an unprivileged user.
                  #   You need to create this user manually (or you can choose
                  #   a user/group combination that already exists, e.g. nobody).
                  CELERYD_USER="ubuntu"
                  CELERYD_GROUP="ubuntu"

                  # If enabled pid and log directories will be created if missing,
                  # and owned by the userid/group configured.
                  CELERY_CREATE_DIRS=1
              • celerybeat.conf (/etc/default/celerybeat)
                • # Absolute or relative path to the 'celery' command:
                  #CELERY_BIN="/usr/local/bin/celery"
                  #CELERY_BIN="/virtualenvs/def/bin/celery"
                  CELERY_BIN="/opt/p27/bin/celery"

                  # App instance to use
                  # comment out this line if you don't use an app
                  CELERY_APP="my_app"
                  # or fully qualified:
                  #CELERY_APP="proj.tasks:app"

                  # Where to chdir at start.
                  CELERYBEAT_CHDIR="/home/user/my_project/"

                  # Extra arguments to celerybeat
                  #CELERYBEAT_OPTS="--schedule=/var/run/celery/celerybeat-schedule"

                  CELERYD_USER="ubuntu"
                  CELERYD_GROUP="ubuntu"

                  # Django
                  export DJANGO_SETTINGS_MODULE="my_app.settings"
                  CELERYD_CHDIR="/home/user/my_project/"
              • wget https://raw.githubusercontent.com/celery/celery/3.1/extra/generic-init.d/celerybeat; chmod+x celerybeat
              • wget https://raw.githubusercontent.com/celery/celery/3.1/extra/generic-init.d/celeryd; chmod+x celeryd
              • install_celery.sh
                • # worker
                  install celeryd.conf /etc/default/celeryd
                  install celeryd /etc/init.d/
                  update-rc.d celeryd defaults

                  # scheduler (a worker is also needed)
                  install celerybeat.conf /etc/default/celerybeat
                  install celerybeat /etc/init.d/
                  update-rc.d celerybeat defaults
              • sudo ./install_celery.sh
              • sudo service celeryd start
              • sudo service celerybeat start
      • Monitoratge / Monitoring
      • Events
        • Real-time processing
        • celery_events.py
          • from celery import Celery

            def my_monitor(app):
                def announce_received_tasks(event):
                    ...    
                def announce_started_tasks(event):
                    ...
                def announce_failed_tasks(event):
                    ...
                def announce_succeeded_tasks(event):
                    ...
                def announce_rejected_tasks(event):
                    ...
                def announce_revoked_tasks(event):
                    ...
                with app.connection() as connection:
                    recv = app.events.Receiver(
                        connection,
                        handlers={
                            "task-received": announce_received_tasks,
                            "task-started": announce_started_tasks,
                            "task-failed": announce_failed_tasks,
                            "task-succeeded": announce_succeeded_tasks,
                            "task-rejected": announce_rejected_tasks,
                            "task-revoked": announce_revoked_tasks,
                            "*": state.event,
                        },
                    )
                    recv.capture(limit=None, timeout=None, wakeup=True)
               
            def main(args):
                broker = "amqp://my_user:my_password@localhost/my_host"
                app = Celery(broker=broker)
                my_monitor(app)

            if __name__ == "__main__":
                main(sys.argv[1:])
        • test_celery_events.py (WIP)
          • from django.test import TestCase

            from mock import patch, Mock
            from celery_events import main

            class CeleryEventsTestCase(TestCase):
                # @patch("celery_events.Celery.events.Receiver._receive")
                @patch("celery_events.Celery.connection")
                def test_main(self, mock_connection):
                    # https://github.com/celery/celery/blob/master/t/unit/events/test_events.py
                    mock_connection = Mock()
                    mock_connection.transport_cls = "memory"

                    #mock_receive = Mock()

                    args = []
                    main(args)
      • Ús / Usage
        • Puntual / Single
          • Option 1: from views.py
            • myproject/my_app/tasks.py
              • from celery import task

                @task
                def do_something(parameter)
                  ...
            • myproject/my_app/views.py
              • from my_app.tasks import do_something

                ...
                resultat_asinc = do_something.delay(parameter_value)
            • manually test (no Celery worker is needed; Celery is only needed when executing functions with .delay())
              • source env/bin/activate
              • ./manage shell
                • from my_app.tasks import *
                • do_something()
            • or, directly from a single command
          • Option 2: from models.py
            • Example 1: image conversion, all local (consider django-stdimage)
              • myproject/my_app/models.py
                • from celery import task
                  import subprocess

                  class Imatge(models.Model):
                      nom = models.CharField(max_length=100)   
                      user = models.ForeignKey( UploadUser )
                      media = models.ImageField(upload_to='ima')
                      converted_media = models.ImageField(upload_to='conv', blank=True)
                     
                      def save(self, *args, **kwargs):
                          # optional parameter to indicate whether perform
                          # conversion or not. Defaults to True
                          do_conversion = kwargs.pop('do_conversion', True)
                         
                          # do something only when the entry is created
                          if not self.pk:
                              super(Imatge, self).save(*args, **kwargs)

                          # do something every time the entry is updated
                          if do_conversion:
                              image_convertida = convert_imatge.delay(self.pk)

                          print "media.name: %s" % self.media.name
                          print "media.file.name: %s" % self.media.file.name
                          print "converted upload_to: %s" % self.converted_media.field.upload_to

                          # then call the parent save:
                          super(Imatge, self).save(*args, **kwargs)
                         
                      def __unicode__(self):
                          return "%s (%s)" % (self.nom, self.media)
                   
                  # no es pot posar a tasks.py perque caldria un import del
                  # model Imatge, i faria un bucle infinit  
                  @task
                  def convert_imatge(imatge_id):
                      imatge = Imatge.objects.get(pk=imatge_id)
                      print "convert_imatge: pk=%s" % imatge.id
                     
                      # input image
                      abs_input_image = imatge.media.file.name

                      # get the upload_to from model definition
                      upload_path = imatge.converted_media.field.upload_to
                      abs_upload_path = os.path.join( settings.MEDIA_ROOT, upload_path )
                      # create it if it does not exist, as it is not automatically created
                      if not os.access(abs_upload_path, os.F_OK):
                          print "creating %s" % abs_upload_path
                          os.mkdir(abs_upload_path)
                      else:
                          print "dir %s already exists" % abs_upload_path
                     
                      # output image
                      output_image = os.path.join(upload_path, 'toto.png')
                      abs_output_image = os.path.join( settings.MEDIA_ROOT, output_image )
                     
                      # command to convert (ImageMagick)
                      convert_command = "convert %s %s" % (abs_input_image, abs_output_image)
                      print convert_command
                      try:
                          # shell=True es per a poder posar tota l'ordre de cop
                          convert_result = subprocess.call(convert_command, shell=True)
                          print "Result: %s" % convert_result
                      except Exception as e:
                          convert_result = None
                          print "Failed to convert image %s to %s" % (abs_input_image, abs_output_image)
                          print "Error: %s" % e
                         
                      # update the name of the generated image
                      imatge.converted_media.name = output_image
                      # do not perform conversion, to avoid infinite loop
                      imatge.save(do_conversion=False)
                     
                      print "converted image width: %s" % imatge.converted_media.width
                      print "converted image height: %s" % imatge.converted_media.height
            • Example 2: image conversion, input image uploaded to AWS S3, output image uploaded to AWS S3 (via a temporary local file):
              • myproject/my_app/models.py
                • from celery import task
                  import subprocess
                  class Imatge(models.Model):
                      nom = models.CharField(max_length=100)    
                      user = models.ForeignKey( UploadUser )
                      media = models.ImageField(upload_to='ima')
                      converted_media = models.ImageField(upload_to='conv', blank=True)
                      
                      def save(self, *args, **kwargs):
                          # optional parameter to indicate whether perform
                          # conversion or not. Defaults to True
                          do_conversion = kwargs.pop('do_conversion', True)
                          
                          # do something only when the entry is created
                          if not self.pk:
                              super(Imatge, self).save(*args, **kwargs)

                          # do something every time the entry is updated
                          if do_conversion:
                              convert_imatge_s3.delay(self.pk)
                        
                          # then call the parent save:
                          super(Imatge, self).save(force_update = True)
                          
                      def __unicode__(self):
                          return "%s (%s)" % (self.nom, self.media)

                  @task
                  def convert_imatge_s3(imatge_id):
                      from django.core.files.uploadedfile import SimpleUploadedFile
                      import tempfile
                     
                      imatge = Imatge.objects.get(pk=imatge_id)
                      print "convert_imatge_s3: pk=%s" % imatge.id

                      # input image
                      url_input_image = imatge.media.url

                      # output image
                      OUTPUT_IMAGE_EXT = 'png'
                      OUTPUT_IMAGE_CONTENT_TYPE = 'image/png'   
                      f_out = tempfile.NamedTemporaryFile(suffix=".%s"%OUTPUT_IMAGE_EXT, delete=False)
                      tmp_output_image = f_out.name

                      # command to convert
                      convert_command = "convert '%s' '%s'" % (url_input_image, tmp_output_image)
                      print convert_command
                      try:
                          # shell=True es per a poder posar tota l'ordre de cop
                          convert_result = subprocess.call(convert_command, shell=True)
                          print "Result: %s" % convert_result
                      except Exception as e:
                          convert_result = None
                          print "Failed command: %s" % (convert_command)
                          print "Error: %s" % e
                         
                      # prepare an object with the generated temporary image
                      # (? why the name of the original image: imatge.media.name?)
                      suf = SimpleUploadedFile(
                                               os.path.split(imatge.media.name)[-1],
                                               f_out.read(),
                                               content_type=OUTPUT_IMAGE_CONTENT_TYPE
                                               )
                     
                      # upload converted image to S3 and set the name.
                      # save set to False to avoid infinite loop
                      imatge.converted_media.save(
                                                  '%s_convertida.%s' % (os.path.splitext(suf.name)[0], OUTPUT_IMAGE_EXT),
                                                  suf,
                                                  save=False
                                                  )
                     
                      # delete temporary output file
                      os.remove(tmp_output_image)
                     
                      # save the parent object because this is an async process.
                      # do not perform conversion, to avoid infinite loop
                      imatge.save(do_conversion=False)
              • myproject/my_app/s3utils.py
                • from storages.backends.s3boto import S3BotoStorage
                  from django.utils.functional import SimpleLazyObject
                  from django.utils.deconstruct import deconstructible
                  from django.conf import settings

                  MediaRootS3BotoStorage = lambda: S3BotoStorage(location=settings.S3_MEDIA_PATH)


                  @deconstructible
                  class NewS3BotoStorage(S3BotoStorage):
                      """
                      Deconstructible subclass to avoid Django 1.7 error:
                      ValueError: Cannot serialize: <storages.backends.s3boto.S3BotoStorage object...
                      """
                      pass
              • myproject/myproject/settings.py
                • # media: uploaded files
                  DEFAULT_FILE_STORAGE = 'my_app.s3utils.MediaRootS3BotoStorage'
                  S3_MEDIA_PATH = "media"
                  MEDIA_ROOT = '/%s/' % S3_MEDIA_PATH
                  MEDIA_URL = "https://%s.s3.amazonaws.com/" % AWS_STORAGE_BUCKET_NAME
        • Puntual, en un moment determinat / Execute once, at given time
        • Periodic tasks
          • In celery 3.1, making django periodic task
          • option 1: schedules from admin (django-celery)
          • option 2: fixed schedules
            • myproject/settings.py
              • # needed by "from celery.schedules import crontab"
                from __future__ import absolute_import
                ...

                # celery
                BROKER_URL = 'amqp://myuser:mypassword@localhost//'

                from datetime import timedelta
                from celery.schedules import crontab
                # if
                CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler',
                # these entries will be included in the database,
                every time the celery beat is restarted CELERYBEAT_SCHEDULE = {
                    'do_something-every-10-seconds': {
                        'task': 'myproject.tasks.do_something',
                        'schedule': timedelta(seconds=10),
                        'args': (16, 16)
                    },
                    'do_something_else-at_2:00_every_day': {
                        'task': 'myproject.tasks.do_something_else',
                        'schedule': crontab(hour='2',minute='0'),
                    },

                }
          • myproject/__init__.py
            • from __future__ import absolute_import

              # This will make sure the app is always imported when
              # Django starts so that shared_task will use this app.
              from myproject.celery import app as celery_app
          • myproject/celery.py
            • from __future__ import absolute_import
              import os
              from celery import Celery
              from django.conf import settings

              # set the default Django settings module for the 'celery' program.
              os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

              app = Celery('myproject')

              # Using a string here means the worker will not have to
              # pickle the object when using Windows.
              app.config_from_object('django.conf:settings')
              app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

              # add the following line to have access to myproject/tasks.py
              app.autodiscover_tasks(lambda: ('myproject',))

              @app.task(bind=True)
              def debug_task(self):
                  print('Request: {0!r}'.format(self.request))
          • myproject/tasks.py
            • from __future__ import absolute_import
              from celery import shared_task

              @shared_task
              def do_something(parameter1, parameter2):
                  # print and logger (console) outputs will be written to log file specified by celery.service (not logfile specified by celerybeat.service)
                  print "[do_something] parameter1=%d parameter2=%d" % (parameter1,parameter2)
                  total = parameter1 + parameter2
                  return "total: %d" % total

              @shared_task
              def do_something_else():
                  # print and logger (console) outputs will be written to log file specified by celery.service (not logfile specified by celerybeat.service)
                  print "[do_something_else]"
                  return True
          • celery -A myproject beat -l info
          • celery -A myproject worker -B -l info

Cron

  • Celery periodic tasks
  • django-cron (Tivix) (github)
    • Installation (one of the following):
      • pip install django_cron
      • pip install -e git://github.com/Tivix/django-cron.git#egg=django_cron
    • From Django 1.7:
      • python manage.py makemigrations django_cron
      • python manage.py migrate
    • settings.py
      • INSTALLED_APPS = [
            #...
            "django_cron",
        ]
    • my_app/cron.py
      • from django.utils.translation import ugettext_lazy as _
        from my_app.all_functions import toto_function
        from django_cron import CronJobBase, Schedule
        import logging
        log = logging.getLogger(__name__)

        class TotoCronJob(CronJobBase):
            RUN_AT_TIMES = ['10:00',]
            schedule = Schedule(run_at_times=RUN_AT_TIMES)
            code = 'my_app.toto_cron_job'    # a unique code

            def do(self):
                log.info("Cron to make toto")
                result = toto_function()
                message = _("Result of toto_function: ") + ", ".join(result)
                return message
      • from django.utils.translation import ugettext_lazy as _
        from my_app.all_functions import toto_function
        from django_cron import CronJobBase, Schedule
        import logging
        log = logging.getLogger(__name__)

        class TotoCronJob(CronJobBase):
            RUN_EVERY_MINS = 120 # every 2 hours
            schedule = Schedule(run_every_mins=RUN_EVERY_MINS)
            code = 'my_app.toto_cron_job'    # a unique code

            def do(self):
                log.info("Cron to make toto")
                result = toto_function()
                message = _("Result of toto_function: ") + ", ".join(result)
                return message
    • settings.py
      • CRON_CLASSES = [
            "my_app.cron.
        TotoCronJob",
        ]
    • executa / run
      • una vegada / once:
        • python manage.py runcrons
      • programat / scheduled:
        • install_crontab.sh
          • #! /bin/bash
            linia_cron="0 * * * * source /opt/PYTHON27/bin/activate; python /var/www/toto_web/manage.py runcrons >> /tmp/cron.log 2>&1; deactivate"
            crontab -l | { cat; echo "${linia_cron}"; } | crontab -
  • django-cron (code.google)

Serialització / Serialization

Importació i exportació / Import and export



  • formats


    xml
    json
    csv
    xls
    xlsx
    (native)
    serialization
    ie
    ie



    django-adaptors

    i

    i


    django-data-importer

    i

    i
    i

    django-import-export tablib -
    ie
    ie
    ie

    djangorestframework

    ie
    ie



    Panda

    e
    e

    django-rest-framework-csv

    ie


    xlrd / xlwt



    ie

    openpyxl




    ie
  • Admin extensions
  • General
  • django-rest-framework-csv
    • Ordered CSVRenderer
      • this implementation is needed in order to keep fields order as specified (in serializer or in data itself), when using @detail_route in ViewSet (otherwise, the suggested methods in Ordered Fields are valid)
      • renderers.py
        • from rest_framework_csv.renderers import CSVRenderer
          from collections import OrderedDict

          class OrderedCSVRenderer (CSVRenderer):
             
              def render(self, data, media_type=None, renderer_context={}, writer_opts=None):
                  if not isinstance(data, list):
                      data = [data]

                 
          # if a serializer is used (serizlizer determines the order), we just take the header from the first element:
                  if data
          [0]:
                     
          renderer_context['header'] = data[0].keys()

                  # if a serializer is not used (data determines the order), we need to get an ordered version of all fields:         #total_od = OrderedDict()
                 
          #for a in data:
                 
          #    total_od.update(a)
                 
          #renderer_context['header'] = total_od.keys()

                  return CSVRenderer.render(self, data, media_type=media_type, renderer_context=renderer_context, writer_opts=writer_opts)
      • serializers.py
        • class MyMainserializer(...

          class MyDetailSerializer(serializers.Serializer):
              # this order will be honoured by JsonRendered, OrderedCSVRenderer
              field_2 = serializers.CharField()
              field_1 = serializers.CharField()
              field_3 = serializers.CharField()
      • views.py (using a serializer)
        • from my_app.renderers import OrderedCSVRenderer
          from my_app.serializers import
          MyMainSerializer, MyDetailSerializer

          class MyViewSet(viewsets.ModelViewSet):
              queryset = MyModel.objects.all()
              serializer_class = MyMainSerializer

              @detail_route( serializer_class = MyDetailSerializer, renderer_classes = (JSONRenderer, OrderedCSVRenderer,) )
              def my_detail(self, request, *args, **kwargs):
                  obj_list = []
                 
                  # build temporary object
                  obj_list = [{"field_1": "value_11",
                               "field_2": "value_12",
                               "field_3": "value_13",
                               },
                              {"field_1": "value_21",
                               "field_2": "value_22",
                               "field_3": "value_23",
                               },
                  ]

                  serializer = self.get_serializer(obj_list, many=True)
                
                  return Response(serializer.data)
      • views.py (not using a serializer)
        • class MyViewSet(viewsets.ModelViewSet):
              queryset = MyModel.objects.all()
              serializer_class = MyMainSerializer
                 
              @detail_route( renderer_classes = (JSONRenderer, OrderedCSVRenderer,) )
              def my_detail(self, request, *args, **kwargs):
                  obj_list = []
                 
                  # build temporary object
                  obj1 = OrderedDict()
                  obj1["field_1"] = "value_11"
                  obj1["field_2"] = "value_12"
                  obj1["field_4"] = "value_14"
                 
                  obj2 = OrderedDict()
                  obj2["field_3"] = "value_23"
                  obj2["field_1"] = "value_21"
                  obj2["field_2"] = "value_22"
                  obj_list = [obj1,obj2]

                  return Response(obj_list)
    • Hierarchical parser
      • parsers.py
        • class HierarchicalCSVParser(BaseParser):
              """
              Parses CSV serialized data.

              The parser assumes the first line contains the column names.
              """

              media_type = 'text/csv'

              def parse(self, stream, media_type=None, parser_context=None):
                  parser_context = parser_context or {}
                  delimiter = parser_context.get('delimiter', ',')

                  try:
                      encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
                      rows = unicode_csv_reader(universal_newlines(stream), delimiter=delimiter, charset=encoding)
                      data = OrderedRows(next(rows))
                      for row in rows:
                          row_data = dict(zip(data.header, row))
                          hierarchical_data = self._csv_convert(row_data)
                          data.append(hierarchical_data)
                      return data
                  except Exception as exc:
                      raise ParseError('CSV parse error - %s' % str(exc))

              def _csv_convert(self,flat_data):
                  first_level_keys = {key.split(".")[0] for key in flat_data.keys()}
                  if list(first_level_keys)[0].isdigit():
                      d = []
                  else:
                      d = {}
                  for first_level_key in first_level_keys:
                      # a subset of the dictionary with only the entries with the
                      # key: first_level_key.* and non empty value
                      subset = {key:value for key, value in flat_data.items() if key.partition(".")[0]==first_level_key and len(value)>0}
                      if len(subset) > 0:
                          at_deepest = subset.keys()[0].partition(".")[1]==''
                          if at_deepest:
                              # end of recursivity
                              d.update(subset)
                          else:
                              # can go deeper
                              # remove the first_level_key
                              flat_second_level_subset = {key.partition(".")[2]:value for key, value in subset.items()}
                              second_level_subset = self._csv_convert(flat_second_level_subset)
                              if first_level_key.isdigit():
                                  # add to the list
                                  d.append(second_level_subset)
                              else:
                                  # add to the dictionary
                                  d[first_level_key] = second_level_subset
                 
                  return d
    • XLSRenderer
      • renderers.py
        • import xlwt
          from datetime import datetime, date

          class XLSRenderer(CSVRenderer):
              media_type = 'application/ms-excel'
              format = 'xls'

              def render(self, data, media_type=None, renderer_context=None, sheetname='First'):       
                  table = self.tablize(data)
                  wb = self.to_workbook(table, sheetname=sheetname)
                  return wb
             
              # source: http://fragmentsofcode.wordpress.com/2009/10/09/xlwt-convenience-methods/
              def to_workbook(self, tabular_data, workbook=None, sheetname=None):
                  """
                  Returns the Excel workbook (creating a new workbook
                  if necessary) with the tabular data written to a worksheet
                  with the name passed in the 'sheetname' parameter (or a
                  default value if sheetname is None or empty).
                  """
                  wb = workbook or xlwt.Workbook(encoding='utf8')
                  if len(sheetname)>31:
                      sheetname = sheetname[:31]
                  ws = wb.add_sheet(sheetname or 'Data')
                  self.to_worksheet(tabular_data, ws)
                  return wb
             
             
              def to_worksheet(self, tabular_data, worksheet):
                  """
                  Writes the tabular data to the worksheet (returns None).
                  Thanks to John Machin for the tip on using enumerate().
                  """
             
                  default_style = xlwt.Style.default_style
                  datetime_style = xlwt.easyxf(num_format_str='dd/mm/yyyy hh:mm')
                  date_style = xlwt.easyxf(num_format_str='dd/mm/yyyy')
             
                  for row, rowdata in enumerate(tabular_data):
                      worksheet_row = worksheet.row(row)
                      for col, val in enumerate(rowdata):
                          if isinstance(val, datetime):
                              val = val.replace(tzinfo=None)
                              style = datetime_style
                          elif isinstance(val, date):
                              style = date_style
                          else:
                              style = default_style
                 
                          worksheet_row.write(col, val, style=style)
      • admin.py
        • from datetime import datetime
          from django.utils.translation import ugettext_lazy as _

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

              def export_xls_from_admin_view(self, request, queryset):
                  serializer = MyModelSerializer(queryset, many=True)
                  wb = XLSRenderer().render(serializer.data, sheetname='MyModel')
                 
                  now = datetime.now()
                  filename = "mymodel_%s.xls" % (now.strftime('%Y%m%d-%H%M%S'))
                 
                  # Django < 1.7
                  #response = HttpResponse(mimetype="application/ms-excel")
                  # Django 1.7
                  response = HttpResponse(content_type="application/ms-excel")
                  response['Content-Disposition'] = 'attachment; filename=%s' % filename
                  wb.save(response)
                 
                  return response
              export_xls_from_admin_view.short_description = _("Export selected items to XLS")
    • XLSParser
      • parsers.py
        • import xlrd

          class HierarchicalXLSParser(HierarchicalCSVParser):
              """
              Parses CSV serialized data.

              The parser assumes the first line contains the column names.
              """

              media_type = 'application/vnd.ms-excel'

              def parse(self, stream, media_type=None, parser_context=None):
                  book = xlrd.open_workbook(file_contents=stream)
                  sheet = book.sheet_by_index(0)

                  data = []
                  header = []
                  try:
                      for row_index in range(sheet.nrows):
                          row = []
                          for col_index in range(sheet.ncols):
                              if row_index==0:
                                  header.append(sheet.cell(row_index,col_index).value)
                              else:
                                  row.append(sheet.cell(row_index,col_index).value)
                          if row_index!=0:
                              row_data = dict(zip(header, row))
                              hierarchical_data = self._csv_convert(row_data)
                              data.append(hierarchical_data)
                      return data
                  except Exception as exc:
                      raise ParseError('HierarchicalXLS parse error - %s' % str(exc))

                 
      • actions.py
        • def parse_uploaded_file(uploaded_file):
              errors = dict()

              if uploaded_file.content_type == 'application/vnd.ms-excel':
                  fitxer = File(uploaded_file)
                  content = fitxer.read()
                  #stream = BytesIO(content)
                  data = HierarchicalXLSParser().parse(content)
                  fitxer.close()
              serializer = EventSerializer(data=data, many=True)
             
              if serializer.is_valid():
                  serializer.save()
              else:
                  return serializer.errors
  • django-data-importer
  • django-import-export (github)
  • django-adaptors
  • Marmir
  • Python Excel: xlwt / xlrd
    • See also: openpyxl
    • Outputting Excel with Django
    • xlwt convenience methods
    • Exporting data to Excel or PDF file in Django
    • huDjango serializers
    • save to:
      • a local file:
        • ...
          wb.save('toto.xls')
      • an http response:
        • ...
          # Django < 1.7:
          #response = HttpResponse(mimetype="application/ms-excel")
          # Django 1.7:
          response = HttpResponse(content_type="application/ms-excel")
          response['Content-Disposition'] = 'attachment; filename=%s' % fname
          wb.save(response)
    • admin.py (using django-adminplus)
      • from django.http import HttpResponse
        import xlwt
        from datetime import datetime, date
        from toto_app.models import Toto1, Toto2

        # source: http://fragmentsofcode.wordpress.com/2009/10/09/xlwt-convenience-methods/
        def to_workbook(tabular_data, workbook=None, sheetname=None):
            """
            Returns the Excel workbook (creating a new workbook
            if necessary) with the tabular data written to a worksheet
            with the name passed in the 'sheetname' parameter (or a
            default value if sheetname is None or empty).
            """
            wb = workbook or xlwt.Workbook(encoding='utf8')
            ws = wb.add_sheet(sheetname or 'Data')
            to_worksheet(tabular_data, ws)
            return wb


        def to_worksheet(tabular_data, worksheet):
            """
            Writes the tabular data to the worksheet (returns None).
            Thanks to John Machin for the tip on using enumerate().
            """

            default_style = xlwt.Style.default_style
            datetime_style = xlwt.easyxf(num_format_str='dd/mm/yyyy hh:mm')
            date_style = xlwt.easyxf(num_format_str='dd/mm/yyyy')

            for row, rowdata in enumerate(tabular_data):
                worksheet_row = worksheet.row(row)
                for col, val in enumerate(rowdata):
                    if isinstance(val, datetime):
                        val = val.replace(tzinfo=None)
                        style = datetime_style
                    elif isinstance(val, date):
                        style = date_style
                    else:
                        style = default_style
           
                    worksheet_row.write(col, val, style=style)

        def xls_to_response(xls, fname):
            response = HttpResponse(mimetype="application/ms-excel")
            response['Content-Disposition'] = 'attachment; filename=%s' % fname
            xls.save(response)
            return response

        @admin.site.register_view('export_database',_("Export Database (xls)"))
        def export_database(request):
            # toto1 model
            table = Toto1.objects.values_list()
            headers = [f.name for f in Toto1._meta.fields]
            table = [headers] + list(table)
            wb = to_workbook(table, sheetname=_("Toto 1"))

            # toto2 model
            table = Toto2.objects.values_list()
            headers = [f.name for f in Toto2._meta.fields]
            table = [headers] + list(table)
            wb = to_workbook(table, workbook=wb, sheetname=_("Toto 2"))
            # toto3 model
            table = Toto3.objects.values_list('field1','field2')
            headers = ['field1_header','field2_header']
            table = [headers] + list(table)
            wb = to_workbook(table, workbook=wb, sheetname=_("Toto 3"))


            return xls_to_response(wb,'toto.xls')

Gràfiques / Charts

SMS

Storage

Etiquetes / Tags

Metadades / Metadata

Push

  • How to build a push system in django?
  • Django Push HTTP Response to users [closed]
  • Django push: Using Server-Sent Events and WebSocket with Django
    • SSE (unidirectional)
    • Websockets (bidirectional)
  • WebSocket
    • uWSGI websockets
    • Django Channels
      • Channels adopted as an official Django project
      • version
        channels Django Python
        1


        2.0
        1.11+
        3.5+

      • connection

        settings
        routing
        view

        gateway

        scope (persistent):
        path, method, headers...
        event
        settings.py



        http
        request
        -

        urls.py
        views.py

        WSGI
        websockets x
        x
        INSTALLED_APPS = [
            ...
            'channels',
        ]

        ASGI_APPLICATION = "myproject.routing.application"
        Routing: routing.py
        Consumers: consumers.py
        Channel layers





        application = URLRouter([
            url(r"^chat/admin/$", AdminChatConsumer),
            url(r"^chat/$", PublicChatConsumer),
        ])

        application = ProtocolTypeRouter({

            "websocket": URLRouter([
                url(r"^chat/admin/$", AdminChatConsumer),
                url(r"^chat/$", PublicChatConsumer),
            ]),

            "telegram": ChattyBotConsumer,
        })

        application = ProtocolTypeRouter({
            "websocket": AuthMiddlewareStack(
                URLRouter([
                    url(r"^front(end)/$", consumers.AsyncChatConsumer),
                ])
            ),
        })
        class ChatConsumer(WebsocketConsumer):

            def connect(self):
                self.username = "Anonymous"
                self.accept()
                self.send(text_data="[Welcome %s!]" % self.username)

            def receive(self, *, text_data):
                if text_data.startswith("/name"):
                    self.username = text_data[5:].strip()
                    self.send(text_data="[set your username to %s]" % self.username)
                else:
                    self.send(text_data=self.username + ": " + text_data)

            def disconnect(self, message):
                pass

        ASGI





        class PingConsumer(AsyncConsumer):
            async def websocket_connect(self, message):
                await self.send({
                    "type": "websocket.accept",
                })

            async def websocket_receive(self, message):
                await asyncio.sleep(1)
                await self.send({
                    "type": "websocket.send",
                    "text": "pong",
                })







        class ChattyBotConsumer(SyncConsumer):

            def telegram_message(self, message):
                """
                Simple echo handler for telegram messages in any chat.
                """
                self.send({
                    "type": "telegram.message",
                    "text": "You said: %s" % message["text"],
                })





      • basic
        websocket
        json websocket
        http

        group
        message type
        SyncConsumer
        AsyncConsumer
        WebsocketConsumer
        AsyncWebsocketConsumer
        JsonWebsocketConsumer
        AsyncJsonWebsocketConsumer AsyncHttpConsumer
        ASGI
        websocket
        websocket.connect
        # when receiving a message of type websocket.connect
        def websocket_connect(self, event)
        # when receiving a message of type websocket.connect
        async def websocket_connect(self, event)
        # when receiving a message of type websocket.connect
        def connect(self)
        # when receiving a message of type websocket.connect
        async def connect(self)



        websocket.receive
        # when receiving a message of type websocket.receive
        def websocket_receive(self, event)
        # when receiving a message of type websocket.receive
        async def websocket_receive(self, event)
        # when receiving a message of type websocket.receive
        def receive(self, text_data=None, bytes_data=None)
        # when receiving a message of type websocket.receive
        async def receive(self, text_data=None, bytes_data=None)
        # when receiving a message of type websocket.receive
        def receive_json(self, content)


        websocket.disconnect


        # when receiving a message of type websocket.disconnect
        def disconnect(self, close_code)
        # when receiving a message of type websocket.disconnect
        async def disconnect(self, close_code)



        websocket.accept
        # send message to websocket, of type websocket.accept
        self.send({"type":"websocket.accept"})
        # send message to websocket, of type websocket.accept
        await self.send({"type":"websocket.accept"})
        # send message to websocket, of type websocket.accept
        self.accept()
        self.accept("subprotocol")
        # send message to websocket, of type websocket.accept
        await self.accept()
        await self.accept("subprotocol")



        websocket.send
        # send message to websocket, of type websocket.send
        self.send({"type":"websocket.send","text":...})
        # send message to websocket, of type websocket.send
        await self.send({"type":"websocket.send",...})
        # send message to websocket, of type websocket.send
        self.send(text_data=...)
        self.send(bytes_data=...)
        # send message to websocket, of type websocket.send
        await self.send(text_data=...)
        await self.send(bytes_data=...)
        # send message to websocket, of type websocket.send
        self.send_json(content=...)


        websocket.close


        self.close()
        self.close(code=...)




        http
        http....







        custom (e.g. for layers)
        chat.message


        def receive...
            # send message to room group, of type chat.message
            async_to_sync(self.channel_layer.group_send)(
                self.room_group_name,
                {"type": "chat_message", ...}
             )
        async def receive...
            # send message to room group, of type chat.message
            await self.channel_layer.group_send(
                self.room_group_name,
                {"type": "chat_message", ...}
             )





        # when receiving a message of type chat.message
        def chat_message(self, event)
        # when receiving a message of type chat.message
        async def chat_message(self, event)



        layers

        action









        join group


        def connect...
            # already set by default: self.channel_name, self.channel_layer
            self.room_name=...
            self.room_group_name=...

             # join room group
             async_to_sync(self.channel_layer.group_add)(
                self.room_group_name,
                self.channel_name
             )
        async def connect...
            # already set by default: self.channel_name, self.channel_layer
            self.room_name=...
            self.room_group_name=...

            # join room group
            await self.channel_layer.group_add(
                self.room_group_name,
                self.channel_name
            )





        leave group


        def disconnect...
            # leave room group
            async_to_sync(self.channel_layer.group_discard)(
                self.room_group_name,
                self.channel_name
            )
        async def disconnect...
            # leave room group
            await self.channel_layer.group_discard(
                self.room_group_name,
                self.channel_name
            )



      • Tutorial
        • Resum / Summary

          project
          app



          settings
          route
          route
          view
          templates
          http
          mysite_ws/settings.py
          mysite_ws/urls.py
          • from django.contrib import admin
            from django.urls import path
            from django.conf.urls import include, url

            urlpatterns = [
                url(r'^chat/', include('chat.urls')),
                path('admin/', admin.site.urls),
            ]
          chat/urls.py
          • from django.conf.urls import url

            from . import views

            urlpatterns = [
                url(r'^$', views.index, name='index'),
                url(r'^(?P<room_name>[^/]+)/$', views.room, name='room'),
            ]
          chat/views.py
          • from django.shortcuts import render
            from django.utils.safestring import mark_safe
            import json

            def index(request):
                return render(request, 'chat/index.html', {})

            def room(request, room_name):
                return render(request, 'chat/room.html', {
                    'room_name_json': mark_safe(json.dumps(room_name))
                })
          chat/templates/chat/index.html
          chat/templates/chat/room.html
          ws
          mysite_ws/settings.py
          • INSTALLED_APPS = [
                'channels',
                ...
            ]

          • ASGI_APPLICATION = "mysite_ws.routing.application"
          • CHANNEL_LAYERS = {
                'default': {
                    'BACKEND': 'channels_redis.core.RedisChannelLayer',
                    'CONFIG': {
                        "hosts": [('127.0.0.1', 6379)],
                    },
                },
            }
          mysite_ws/routing.py
          • from channels.auth import AuthMiddlewareStack
            from channels.routing import ProtocolTypeRouter, URLRouter
            import chat.routing

            application = ProtocolTypeRouter({
                # (http->"django views" is added by default)
                'websocket': AuthMiddlewareStack(
                    URLRouter(
                        chat.routing.websocket_urlpatterns
                    )
                ),
            })
          chat/routing.py
          • from django.conf.urls import url

            from . import consumers

            websocket_urlpatterns = [
                url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer),
            ]
          chat/consumers.py
          • from channels.generic.websocket import WebsocketConsumer
            import json

            # synchronous consumer
            class ChatConsumer(WebsocketConsumer):
                def connect(self):
                    self.accept()

                def disconnect(self, close_code):
                    pass

                def receive(self, text_data):
                    text_data_json = json.loads(text_data)
                    message = text_data_json['message']

                    self.send(text_data=json.dumps({
                        'message': message
                    }))
          • from asgiref.sync import async_to_sync
            from channels.generic.websocket import WebsocketConsumer
            import json


            # synchronous consumer

            class ChatConsumer(WebsocketConsumer):
                def connect(self):
                    self.room_name = self.scope['url_route']['kwargs']['room_name']
                    self.room_group_name = 'chat_%s' % self.room_name

                    # Join room group
                    async_to_sync(self.channel_layer.group_add)(
                        self.room_group_name,
                        self.channel_name
                    )

                    self.accept()

                def disconnect(self, close_code):
                    # Leave room group
                    async_to_sync(self.channel_layer.group_discard)(
                        self.room_group_name,
                        self.channel_name
                    )

                # Receive message from WebSocket
                def receive(self, text_data):
                    text_data_json = json.loads(text_data)
                    message = text_data_json['message']

                    # Send message to room group
                    async_to_sync(self.channel_layer.group_send)(
                        self.room_group_name,
                        {
                            'type': 'chat_message',
                            'message': message
                        }
                    )

                # Receive message from room group
                def chat_message(self, event):
                    message = event['message']

                    # Send message to WebSocket
                    self.send(text_data=json.dumps({
                        'message': message
                    }))
          • ...
            # asynchronous consumer

        • Tutorial Part 1: Basic setup
          • Creating a project
            • create a general directory, install Python3 with virtualenv and install Django
              • mkdir -p ~/src/mysite_ws
              • cd ~/src/mysite_ws
              • virtualenv-3.5 env3
              • source env3/bin/activate
              • pip install --upgrade pip
              • pip install django
              • pip install -U channels
            • create a django project: mysite_ws
              • django-admin startproject mysite_ws .
          • Creating the chat app
            • ./manage.py startapp chat
            • mysite_ws/mysite_ws/settings.py
              • INSTALLED_APPS = [
                    'chat',
                    ...
                ]
          • Add the index view
            • chat/templates/chat/index.html
            • chat/views.py
            • chat/urls.py
            • mysite_ws/urls.py
          • Integrate the Channels library
            • mysite_ws/routing.py
            • mysite_ws/settings.py
              • INSTALLED_APPS = [
                    'channels',
                    ...
                ]
              • # Channels
                ASGI_APPLICATION = "mysite_ws.routing.application"
        • Tutorial Part 2: Implement a Chat Server
          • Add the room view
            • chat/templates/chat/room.html
            • chat/views.py
            • chat/urls.py
          • Write your first consumer
            • chat/consumers.py
            • chat/routing.py
            • mysite_ws/routing.py
          • Enable a channel layer
            • to allow communication between several consumers
            • abstractions
              • channel: mailbox where messages can be sent to
              • group: group of related channels
            • using Redis
              • native
              • with Docker
                • sudo systemctl start docker.service
                • docker run -p 6379:6379 -d redis:2.8
              • pip install channels_redis
            • check
              • $ python3 manage.py shell
                >>> import channels.layers
                >>> channel_layer = channels.layers.get_channel_layer()
                >>> from asgiref.sync import async_to_sync
                >>> async_to_sync(channel_layer.send)('test_channel', {'type': 'hello'})
                >>> async_to_sync(channel_layer.receive)('test_channel')
                {'type': 'hello'}
        • Tutorial Part 3: Rewrite Chat Server as Asynchronous
          • Rewrite the consumer to be asynchronous
            • chat/consumers.py
          • ...
        • Tutorial Part 4: Automated Testing
          • Testing the views
            • Install
              • Chrome
              • chromedriver and put it somewhere visible by PATH environment variable
              • selenium
                • pip install selenium
            • chat/tests.py
        • ...
      • Consumers
      • Routing
      • Database access
      • Channel layers
      • Sessions
      • Authentication
      • Security
        • OriginValidator
        • AllowedHostsOriginValidator
          • takes same values as defined in settings.ALLOWED_HOSTS
      • Testing
      • Worker and background tasks
        • ./manage.py runworker ...
      • Desplegament / Deployment
            • Redis
              • /var/log/messages
                • Jul  6 11:08:06 ip-172-31-21-155 daphne: 2021-07-06 11:08:06,319 ERROR    Exception inside application: ERR Error running script (call to f_00162c6a9d31b171bb0830f1fd769a379c6723d1): @user_script:6: @user_script: 6: -MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.
              • /var/log/redis/redis.conf
                • 2792:M 06 Jul 11:08:01.050 * 10 changes in 300 seconds. Saving...
                  2792:M 06 Jul 11:08:01.051 # Can't save in background: fork: Cannot allocate memory
      • ...
    • Packages related to WebSockets

Amazon AWS

  • Django-compressor: how to write to S3, read from CloudFront?
  • Fun with Django storage backends
    • Protect some file storage
  • Scaling Django Apps With Amazon AWS
  • Amazon CLI
  • Boto
  • Amazon S3 (django-storages)
    • See also: Media
    • S3Storage
    • S3boto (boto)
      • Instal·lació / Installation
      • Info
      • settings.py
        • INSTALLED_APPS = (
              ...
              'storages',
              ...
          )

          ####### general
          AWS_ACCESS_KEY_ID = '...'
          AWS_SECRET_ACCESS_KEY = '...'
          AWS_STORAGE_BUCKET_NAME = '...'

          # general optional
          AWS_QUERYSTRING_AUTH = False
          AWS_HEADERS = {
            'Cache-Control': 'max-age=86400',
          }
          ####### media: uploaded files
          #DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
          DEFAULT_FILE_STORAGE = 'myapp.s3utils.MediaRootS3BotoStorage'
          S3_MEDIA_PATH = "media"
          MEDIA_ROOT = '/%s/' %
          S3_MEDIA_PATH

          # option 1: retrieve media from s3:
          # no config is needed
          #MEDIA_URL = "https://%s.s3.amazonaws.com/" % AWS_STORAGE_BUCKET_NAME

          # option 2: retrieve media from cloudfront:
          CLOUDFRONT_DOMAIN = 'xxxxxxx.cloudfront.net'
          MEDIA_URL = 'http://%s/%s/' % (CLOUDFRONT_DOMAIN, S3_MEDIA_PATH)
          AWS_S3_CUSTOM_DOMAIN =
          'xxxxxxx.cloudfront.net'
          ####### static: static files
          #STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
          STATICFILES_STORAGE = 'myapp.
          s3utils.StaticRootS3BotoStorage'
          S3_STATIC_PATH = "static"
          STATIC_ROOT = ''
          # option 1: retrieve static from s3:
          STATIC_URL = "https://%s.s3.amazonaws.com/" % AWS_STORAGE_BUCKET_NAME
      • myapp/s3utils.py (alternativa / alternative: django-s3-folder-storage)
        • from storages.backends.s3boto import S3BotoStorage
          from django.utils.functional import SimpleLazyObject
          from django.utils.deconstruct import deconstructible
          from django.conf import settings
          MediaRootS3BotoStorage = lambda: S3BotoStorage(location=settings.S3_MEDIA_PATH)
          StaticRootS3BotoStorage = lambda: S3BotoStorage(location=settings.S3_STATIC_PATH)

          @deconstructible
          class NewS3BotoStorage(S3BotoStorage):
              """
              Deconstructible subclass to avoid Django 1.7 error:
              ValueError: Cannot serialize: <storages.backends.s3boto.S3BotoStorage object...
              """
              pass
      • myapp/models.py (to override settings.DEFAULT_FILE_STORAGE)
        • from myapp.s3utils import NewS3BotoStorage

          class MyModel(models.Model):
              poster = models.ImageField( storage=NewS3BotoStorage( bucket=settings.DEFAULT_BUCKET_NAME ), upload_to='posters')
      • Multiple S3 buckets
        • How to Dynamically Select Storage in Django FileField

          • models.py
            myapp/s3utils.py
            fixed bucket name (from settings) and location (from settings):
            settings.AWS_STORAGE_BUCKET_NAME
            settings.AWS_LOCATION
            class MyModel(models.Model):
                image = models.FileField(
                    storage=S3Boto3Storage(),
                    upload_to='mydir',
                )


            fixed bucket name (from settings) and location (not from settings):
            settings.AWS_STORAGE_BUCKET_NAME
            class MyModel(models.Model):
                image = models.FileField(
                    storage=MyS3Boto3Storage(),
                    upload_to='mydir',
                )
            from storages.backends.s3boto3 import S3Boto3Storage

            @deconstructible
            class MyS3Boto3Storage(S3Boto3Storage):
                location='mylocation'
            dynamic bucket name class MyModel(models.Model):
                image = MyDynamicFileField(
                    storage=MyConfigurableS3Boto3Storage(),
                    upload_to='mydir',
                )
            from django.db import models
            from django.db.models.fields.files import FieldFile
            from storages.backends.s3boto3 import S3Boto3Storage

            @deconstructible
            class MyConfigurableS3Boto3Storage(S3Boto3Storage):
                def __init__(self, *args, **kwargs):
                    # if not passed as kwargs, get values from settings
                    kwargs['bucket_name'] = kwargs.get('bucket_name', getattr(settings, 'AWS_STORAGE_BUCKET_NAME'))
                    kwargs['file_overwrite'] = kwargs.get('file_overwrite', getattr(settings, 'AWS_S3_FILE_OVERWRITE'))
                    kwargs['custom_domain'] = kwargs.get('custom_domain', getattr(settings, 'AWS_S3_CUSTOM_DOMAIN'))

                    super(MyConfigurableS3Boto3Storage, self).__init__(*args, **kwargs)

            class MyDynamicFieldFile(FieldFile):
                def __init__(self, instance, field, name):
                    super(MyDynamicFieldFile, self).__init__(
                       instance, field, name
                    )
                    # get bucket_name, custom_domain (e.g. may be based on instance, of type MyModel)
                    bucket_name, custom_domain = _get_storage(instance)

                    if bucket_name and custom_domain:
                        # use storage with specific bucket_name and custom_domain             self.storage = MyConfigurableS3Boto3Storage(bucket_name=bucket_name, custom_domain=custom_domain)
                    else:
                        # use storage with default values             self.storage = MyConfigurableS3Boto3Storage()

            class MyDynamicFileField(models.FileField):
                attr_class = MyDynamicFieldFile

                def pre_save(self, model_instance, ):
                    bucket_name, custom_domain = _get_storage(model_instance)
                    if bucket_name and custom_domain:
                        # use storage with specific bucket_name and custom_domain
                        self.storage =
            MyConfigurableS3Boto3Storage(bucket_name=bucket_name, custom_domain=custom_domain)
                    else:
                        # use storage with default values
                        self.storage =
            MyConfigurableS3Boto3Storage()
                   
                    file = super(
            MyDynamicFileField, self
                                 ).pre_save(model_instance, add)
                   
                    return file

        • django-storages with multiple S3 Buckets
        • my_app/s3utils.py
          • from storages.backends.s3boto3 import S3Boto3Storage
            from django.conf import settings
            from django.utils.deconstruct import deconstructible

            @deconstructible
            class MyFirstBucketS3Storage(S3Boto3Storage):
                def __init__(self, *args, **kwargs):
                    kwargs['bucket'] = 'myfirstbucket'
                    kwargs['file_overwrite'] = False
                    kwargs['custom_domain'] = 'myfirstbucket.mydomain.com'

                    super(MyFirstBucketS3Storage, self).__init__(*args, **kwargs)


            @deconstructible
            class MySecondBucketS3Storage(S3Boto3Storage):
                def __init__(self, *args, **kwargs):
                    kwargs['bucket'] = 'mysecondbucket'
                    kwargs['file_overwrite'] = False
                    kwargs['custom_domain'] = 'mysecondbucket.mydomain.com'

                    super(MySecondBucketS3Storage, self).__init__(*args, **kwargs)
        • my_app/models.py
          • from s3utils import MyFirstS3Storage, MySecondS3Storage

            class MyModel(models.Model):
                my_first_field = models.FileField(storage=MyFirstS3Storage(), upload_to=...)
                my_second_field = models.FileField(storage=MySecondS3Storage(), upload_to=...)
                my_other_field =
            models.FileField() # uses default storage
      • Views
      • Problemes / Problems
        • [Errno 104] Connection reset by peer
        • les URLs de l'S3BotoStorage són molt llargues / URLs from S3BotoStorage are very long
          • Using django-storages and the s3boto backend: x-amz-security-token is appended which I do not want
          • Solució / Solution 1:
            • pot ser que encara quedi x-amz-security-token / maybe x-amz-security-token is still present
            • settings.py
              • # remove querystrings in AWS S3 urls
                AWS_QUERYSTRING_AUTH = False
                # not working to avoid x-amz-security-token?:
                #AWS_S3_SECURE_URLS = False
          • Solució / Solution 2:
            • no afegeix ni x-amz-security-token / does not append even x-amz-security-token
            • settings.py
              • AWS_STORAGE_BUCKET_NAME = 'mybucketname'
                AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
        • Upgrading to Django 1.7. Getting error: Cannot serialize: <storages.backends.s3boto.S3BotoStorage object
        • EmailMessage attach_file(): NotImplementedError
          • Solving django-storage NotImplementedError with Amazon S3
          • Solució / Solution
            • .py
              • from django.core.files.storage import default_storage as storage
                 
                class NewEmailMessage(EmailMessage):

                    def attach_file(self, name, mimetype=None):
                        """Attaches a file from the filesystem."""
                        filename = os.path.basename(name)
                        #with open(path, 'rb') as f:
                        with storage.open(name) as f:
                            content = f.read()
                        self.attach(filename, content, mimetype)
            • .py
              • from ... import NewEmailMessage
                ...

                @receiver(post_save, sender=MyModel)
                def mymodel_post_save(sender, instance, **kwargs):
                ...
                    msg = NewEmailMessage(...)
                    if instance.myfile:
                        msg.attach_file(instance.myfile.name)

                    msg.send(False)
      • Django-compressor: how to write to S3, read from CloudFront?
  • Amazon Cloudfront

http://www.francescpinyol.cat/django.html
Primera versió: / First version: 18.XII.2012
Darrera modificació: 22 de novembre de 2024 / Last update: 22nd November 2024

FPM

Valid HTML 4.01!