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',