Django
|
Índex
|
|
|
- Django
- Alternatives
- Instal·lació / Installation
- using pyenv
- install pyenv
- cd ~/src
- create virtualenv:
pyenv virtualenv 3.10 mysite-3.10
- temporarily use created virtualenv
- install django
- create django project (estructura de directoris)
- 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
- 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
|
|
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
- squash
your migrations
- 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
- 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.
-
|
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
- Django
project optimization guide (part 1)
- Django logging, Django Debug Toolbar, Silk
- Profiling
- Info
- Django logging
- Django Debug Toolbar
- Silk
- settings.py
INSTALLED_APPS
= [
...
'silk',
...
]
MIDDLEWARE = [
...
'silk.middleware.SilkyMiddleware',
...
]
SILKY_PYTHON_PROFILER = True
- Load testing
- Throttling
- 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
|
polls
__init__.py
|
models.py
|
tests.py
|
views.py
|
urls.py
|
|
.project
|
.pydevproject
|
|
|
- project:
django-admin .py
startproject
mysite
- application:
python
manage.py
startapp polls
- Eclipse files (PyDev)
-
- Creació d'un projecte / Start a project (a project contains
several apps)
cd
[~/src/]djcode
- create the project ("mysite"):
- configuració de les
bases de dades /
database setup:
- SQLite
- 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:
- ./esborra_base_dades.sh
#!/bin/bash
mysql -u root -p <<EOF
DROP DATABASE datbase_name;
EOF
- ./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
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:
- 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:
- other manage.py
functions:
- list of available functions
- call the API from command line:
- if you are using virtualenv:
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:
- 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
- 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
- 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
- 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
- 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
- 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
- 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
source env/bin/activate
pip install --upgrade pip
- install django
- 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
|
|
|
|
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
|
|
generic
class-based:
from django.views import generic
|
generic.edit.FormView
|
|
|
|
|
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
|
|
|
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 |
|
|
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
- Manager
- create
- update
my_instance.save()
- only specified fields:
my_instance.save(update_fields=[...])
- 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
- 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
my_jsonfield = JSONField(... default=list,
blank=True)
- admin
- 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
- 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
- FieldError: Field 'content_object' does not
generate an automatic reverse relation and
therefore cannot be used for reverse querying.
If it is a GenericForeignKey, consider adding a
GenericRelation.
- OneToOneField
import django.db.models
class Tata(models.Model):
# on_delete per a no tenir
cascade
toto_field =
models.OneToOneField(Toto, blank=True, null=True,
on_delete=django.db.models.SET_NULL)
-
|
models.py
|
admin
|
djangorestframework
|
|
|
|
|
|
drf-swagger
|
hierarchical
|
|
separate
window
|
nested
serializer
|
nested
serializer appears as a string (?)
|
flat
|
not use
inheritance
|
|
flattened at
serializer
|
|
use multiple
inheritance
|
|
|
|
- OneToOneField
and
Deleting
from
django.db.models.signals import post_delete
from django.dispatch.dispatcher import
receiver
@receiver(post_delete, sender=Profile)
def post_delete_user(sender, instance, *args,
**kwargs):
if instance.user: # just in
case user is not specified
instance.user.delete()
- Admin
- Djangorestframework
- 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
- 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
- settings.py
MIGRATION_MODULES
= {
'sites':
'my_project.migrations.sites',
}
python manage.py makemigrations
sites
- will create
my_project/migrations/sites/ 0001_initial.py
python manage.py makemigrations
--empty sites
- will create
my_project/migrations/sites/ 0002_auto_yyyymmdd_hhmm.py
- 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
- 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
- OR
- AND
- 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
- This QueryDict instance is immutable when doing a POST with
application/x-www-form-urlencoded (not a problem with
multipart nor json)
- 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
- 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)
- 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:
- navegació per dates / navigation by date:
- date_hierarchy
- dependències / dependencies
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)
- 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',
)
- 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
- Only for production / integration with Apache or Nginx:
- 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
- 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
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>
- reference
to
static file
- 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',
)
- 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))
- 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!" />
- 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):
- absolute (includes MEDIA_ROOT, if used storage is
FileSystemStorage or derived; when using S3BotoStorage,
it will be the same as imatge.media.name):
- Visualitza imatges / Display images:
- AdminImageWidget
- usage
- simplified version
- ManyToMany
- 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:
|
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
|
|
- 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
- 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 *
- ...
|
|
- 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
- Lectura just després de l'escriptura / Reading just
before writing
- 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:
- ...
- Migració / Migration (e.g. from SQLite to MariaDB)
- with original database (e.g. SQLite)
python manage.py dumpdata
[--exclude=auth] --exclude=contenttypes
--exclude=auth.permission > dump_$(date
'+%Y%m%d_%H%M').json
- change DATABASE in your settings.py
python
manage.py syncdb
python manage.py migrate
python manage.py loaddata
dump.json
- Problemes / Problems
- Migration (model modification)
-
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:
- 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
- ...
- commit and release
- deploy changes to all your environments
- deploy files
- on remote (only if some migration files where
created after the squash):
- 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)
- commit and release
- 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
- settings.py
INSTALLED_APPS = (
...
#'south',
)
cd migration
rm -f ????_* __init__.pyc
python manage.py makemigrations
python manage.py migrate
- First time
- create database (for base Django):
- start migrations (for applications):
python manage.py makemigrations
python manage.py migrate
- create a superuser:
- Next times
- modify your model
python manage.py makemigrations [my_app]
[python manage.py migrate [my_app] --list]
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
- remove and create database (PostgreSQL)
sudo su - postgres
database_name="..."
dropdb ${database_name}
- cre
atedb
${database_name}
- migrate
./manage.py migrate
- create admin user
./manage createsuperuser
- scenario 2: keep database
- update database:
./manage.py makemigrations
./manage.py migrate
- 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
- 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
- create the initial migration:
./manage.py makemigrations
- fake the initial migration:
./manage.py migrate
--fake-initial
./manage.py showmigrations
- to remove all migrations and start from scratch:
- remove the project database:
mysql -P 3307 -u root -p
DROP DATABASE myprojectdb;
- create the database:
mysql -P 3307 -u root -p
CREATE DATABASE myprojectdb;
GRANT ALL ON myprojectdb.* TO
'myuser'@'localhost' IDENTIFIED BY
'mypassword';
FLUSH PRIVILEGES;
- cd my_app/migrations
- rm -f ????_*
- cd ../..
- python manage.py migrate
- python manage.py makemigrations
- 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
- modify your model
python manage.py
schemamigration my_app --auto
python manage.py
migrate my_app (instead of python manage.py
syncdb)
- if further modifications are done in your model,
and you don't want to generate another migration:
python manage.py
schemamigration my_app --auto --update
python manage.py
migrate my_app
- Llista de migracions / List migrations:
python manage.py migrate --list
- Evolution
- SQL
- No SQL
|
|
- 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
|
- 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
- request header:
Cache-Control:
no-cache
- 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
|
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:
|
|
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):
- make sure that your mod_wsgi version matches the
Python version:
- ldd /etc/httpd/modules/mod_wsgi.so
- (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
- 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
- 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>
- 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:
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
- (needed? no)
modify wsgi.py:
import sys
sys.path.append('/home/user/src/djcode_1.4/mysite/')
- 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
- Copieu els
fitxers estàtics / Copy the static files
- Media files
- 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
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
- /var/log/messages (or
journalctl
-f )
uwsgi: --
unavailable modifier
requested: 0 --
- Solució / Solution
- 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
-
client
error code |
logs
|
info |
solució / solution |
|
/var/log/nginx/error.log |
/var/log/messages
(journalctl
-f) |
django logs |
|
|
415 Unsupported media type
"application/json" in request. |
|
|
|
|
- views.py
- parser_classes=(JSONParser,)
- my_test.py
- res = self.client.post(
...
format="json",
)
|
500 Internal Server Error |
|
uwsgi: --- no python application
found, check your startup logs for errors
--- |
|
|
|
... |
|
uwsgi[...]: bind(): No such file or
directory [core/socket.c line 230] |
|
|
|
502 Bad Gateway |
|
uwsgi: -- unavailable modifier
requested: 0 -- |
|
|
|
502 Bad Gateway |
[error] 59921#59921: *4 connect() to
unix:///var/lib/uwsgi/....sock failed
(111: Connection refused)
while connecting to upstream, client:
x.x.x.x, server: xxxx, request: "GET
/admin HTTP/1.1",
upstream:
"uwsgi://unix:///var/lib/uwsgi/....sock:",
host: "x.x.x.x" |
|
|
uwsgi
plugin "python3" works with Python 3.6 and
not with Python3.8 |
|
502 Bad Gateway |
[error] 2223#0: *131046 connect() to
unix:///var/lib/uwsgi/....sock failed (11:
Resource temporarily unavailable)
while connecting to upstream |
|
|
Resource
temporarily unavailable using uwsgi +
nginx |
- sysctl -w net.core.somaxconn=2048
- uwsgi.ini
|
502
Bad Gateway |
[error] 8211#0: *2082953 upstream
prematurely closed connection while
reading response header from upstream ... |
kernel: Out of memory: Kill process
25020 (uwsgi) score 785 or sacrifice child
...
uwsgi: DAMN ! worker 2 (pid: 25020) died,
killed by signal 9 :( trying respawn ...
uwsgi: Respawned uWSGI worker 2 (new pid:
25117) |
|
|
|
...
|
|
|
[Errno 12] Cannot allocate memory |
Nginx,
unable to fork: Cannot allocate memory
|
- myvassal.ini
- # respawn worker after 100
requests
max_requests=100
- limit-as=1024
- add
swap memory
|
504 Gateway Timeout |
|
|
|
The
Case of the Mysterious AWS ELB 504 Errors |
- Timeout
- nginx.conf
- uwsgi_read_timeout 180s;
- client_header_timeout = 180s;
- python
- @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
}
)
|
...
(504?) |
|
|
timeout during read(65536) on
wsgi.input |
|
-
Timeout
- myvassal.ini
- [uwsgi]
socket-timeout = 300
|
... 499 (related to the next
one?) |
|
uwsgi: [uwsgi-body-read] Error
reading 57344 bytes. Content-Length:
3691308799
consumed: 2298748928 left: 1392559871
message: Client closed connection
|
email with Django 500 error:
Internal Server Error: ...
UnreadablePostError
at ...
error during read(65536) on wsgi.input
|
|
|
...
(503?, 499?) |
|
SIGPIPE: writing to a closed
pipe/socket/fd (probably the client
disconnected) on request ...
uwsgi_response_writev_headers_and_body_do():
Broken pipe [core/writer.c line 306]
during ...
uwsgi: IOError: write error |
|
|
- nginx.conf
- # when a client closes the
connection then keep the channel to
uwsgi open. Otherwise uwsgi throws
an IOError
uwsgi_ignore_client_abort on;
- myvassal.ini
- ignore-sigpipe = true
ignore-write-errors = true
disable-write-exception = true
|
|
|
|
|
|
|
- Memory leak
[Errno
12] Cannot allocate memory
- django logs:
timeout during read(65536) on wsgi.input
- django logs:
Internal Server Error: ...
UnreadablePostError at ...
error during
read(65536) on wsgi.input
- /var/log/messages:
Sep 14 21:55:32 ip-x-x-x-x kernel: Out of
memory: Kill process 9257 (uwsgi) score 647 or
sacrifice child
/var/log/nginx/error.log: 2020/09/14 21:55:33
[error] 8211#0: *2082953 upstream prematurely
closed connection while reading response header
from upstream ...
- Solució / Solution:
sudo systemctl restart
emperor.uwsgi.service
- add swap memory (EC2 swap)
- /var/log/messages:
SIGPIPE:
writing to a closed pipe/socket/fd (probably the
client disconnected) on request ...
...
uwsgi: IOError: write error
- 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, ...)
|
|
- 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
%}
- 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/
- Exemples / Examples
- Personalitza l'aspecte de l'admin
|
Forms
|
|
Seguretat / Security
|
- Contrasenyes / Passwords
- CSRF (Cross Site Request Forgery)
|
Usuaris / Users
|
- Usuaris / Users (used
for authentication, e.g. in Tastypie)
(també disponible a / also available from
http://127.0.0.1:8000/admin/auth/user/ ):
- Usuaris i admin /
Users and admin
- Creació / Creation
# avoid
direct import of User (pylint-django
error)
# from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
usuari =
get_user_model().objects.create_user("usuariprimer",
"usuari@toto.org")
from django.contrib.auth
import get_user_model
class MyModelSerializer(serializers.ModelSerializer):
class Meta(object):
# NOTE: we
cannot use model = settings.AUTH_USER_MODEL because an
error would be raised:
#
AttributeError: 'str' object has no attribute '_meta'
model = get_user_model()
from django.conf import
settings
@receiver(..., sender=settings.AUTH_USER_MODEL)
from django.conf import
settings
class MyModel(models.Model):
myfiled_1 = models.ForeignKey(settings.AUTH_USER_MODEL,
...)
myfield_2 = models.OneToOneField(settings.AUTH_USER_MODEL,
...)
python manage.py shell
>>> from django.contrib.auth.models import
User
>>> user = User.objects.create_user("nom_usuari","adreca@usuari","contrasenya")
>>> user.is_staff = True
>>> user.save()
- Contrasenyes / Passwords
- Test:
curl
--user nom_usuari:contrasenya ...
- Superuser (admin)
- Django
tips:
extending the User model
- Autenticació
d'usuari
/ User authentication
- Autenticació de servidors / Server authentication
- User
authentication
in Django (1.4)
- Authentication: verifies that a user is who he
claims to be
- Authorization: determines what an authenticated
user is allowed to do
-
|
|
Django
|
channels
|
django-rest-framework
authentication
|
|
|
(default)
|
django-allauth
|
|
(default)
|
djangorestframework-jwt |
djangorestframework-simplejwt |
django-rest-auth
/ dj-rest-auth
|
|
|
authentication |
authentication
|
social
authentication
|
|
authentication |
authorization
(permissions)
|
authentication |
|
authentication
|
registration
|
social
authentication
|
|
|
verifies that
a user is who he claims to be
|
|
|
|
|
determines
what an authenticated user is allowed to do
|
|
|
|
|
|
pip install
|
|
|
django-allauth
|
|
|
|
djangorestframework-jwt |
djangorestframework-simplejwt |
django-rest-auth
/ dj-rest-auth
|
settings.py
|
INSTALLED_APPS
|
- 'django.contrib.auth'
- 'django.contrib.contenttypes'
|
- 'django.contrib.sites'
- 'allauth'
- 'allauth.account'
|
- ...
- # also adds admin section "Social
Accounts":
'allauth.socialaccount'
- 'allauth.socialaccount.providers....'
|
|
|
|
|
- # only if i18n is used
'rest_framework_simplejwt',
|
- 'rest_framework'
- 'rest_framework.authtoken'
- 'rest_auth'
- 'dj_rest_auth'
|
- ...
- 'allauth'
- 'allauth.account'
- 'rest_auth.registration'
- 'dj_rest_auth.registration'
|
- ...
- 'allauth.socialaccount'
- 'allauth.socialaccount.providers.facebook'
|
MIDDLEWARE_CLASSES
|
- 'SessionMiddleware'
- 'AuthenticationMiddleware'
- 'SessionAuthenticationMiddleware'
|
|
|
|
|
|
|
|
|
|
|
AUTHENTICATION_BACKENDS
|
- 'django.contrib.auth.backends.ModelBackend'
- username (USERNAME_FIELD) /
password
- 'django.contrib.auth.backends.RemoteUserBackend'
|
- # Needed to login by username in
Django admin, regardless of `allauth`
'django.contrib.auth.backends.ModelBackend',
- # `allauth` specific authentication
methods, such as login by e-mail
'allauth.account.auth_backends.AuthenticationBackend',
|
|
|
|
|
|
|
|
|
|
TEMPLATE_CONTEXT_PROCESSORS
(<1.8)
TEMPLATES[].OPTIONS.context_processors
(>=1.8) |
- 'django.core.context_processors.request'
|
|
...
|
|
|
|
|
|
|
|
- 'allauth.socialaccount.context_processors.socialaccount'
|
REST_FRAMEWORK
= {...}
|
|
|
|
|
'DEFAULT_AUTHENTICATION_CLASSES': (...)
|
'DEFAULT_PERMISSION_CLASSES':
(...)
|
'DEFAULT_AUTHENTICATION_CLASSES': (...)
- 'rest_framework_jwt.authentication.JSONWebTokenAuthentication'
|
'DEFAULT_AUTHENTICATION_CLASSES': (...)
- 'rest_framework_simplejwt.authentication.JWTAuthentication',
|
'DEFAULT_AUTHENTICATION_CLASSES':
(...)
|
|
|
(specific)
|
- USERNAME_FIELD = ...
- AUTH_USER_MODEL = ...
|
- SITE_ID = 1
- ACCOUNT_*
- ACCOUNT_AUTHENTICATION_METHOD =
'username'
- ACCOUNT_EMAIL_REQUIRED = True
- ACCOUNT_EMAIL_VERIFICATION =
'optional'
- ACCOUNT_EMAIL_SUBJECT_PREFIX =
'[...]'
- ACCOUNT_PASSWORD_MIN_LENGTH = 6
- ACCOUNT_ADAPTER
- ...
|
|
|
|
|
- JWT_AUTH
- 'JWT_EXPIRATION_DELTA':
datetime.timedelta(seconds=3600)
- 'JWT_ALLOW_REFRESH': True
- 'JWT_RESPONSE_PAYLOAD_HANDLER':
'my_app.utils.jwt_response_payload_handler'
|
|
- REST_AUTH_SERIALIZERS
- 'LOGIN_SERIALIZER':
- 'TOKEN_SERIALIZER':
- 'USER_DETAILS_SERIALIZER':
- 'PASSWORD_RESET_SERIALIZER':
- 'PASSWORD_RESET_CONFIRM_SERIALIZER':
- 'PASSWORD_CHANGE_SERIALIZER':
- REST_SESSION_LOGIN
- OLD_PASSWORD_FIELD_ENABLED
|
|
|
views.py /
consumers.py
|
|
|
|
|
from
channels.auth import login
class ChatConsumer(AsyncWebsocketConsumer):
...
async def receive(self,
text_data):
...
#
login the user to this session.
await login(self.scope,
user)
#
save the session (if the session backend
does not access the db you can use
`sync_to_async`)
await
database_sync_to_async(self.scope["session"].save)()
---
from asgiref.sync import async_to_sync
from channels.auth import login
class SyncChatConsumer(WebsocketConsumer):
...
def receive(self,
text_data):
...
async_to_sync(login)(self.scope,
user)
self.scope["session"].save()
|
|
from
rest_framework.exceptions import
PermissionDenied
class MyModelViewSet(viewsets.ModelViewSet):
# affects all methods in
the class
permission_classes
= (permissions.IsAuthenticated,)
# specific permissions
for create method
def create(self, request,
*args, **kwargs):
#
only staff users can create objects
if not request.user.is_staff:
raise PermissionDenied("Only
staff users can create objects.")
return viewsets.ModelViewSet.create(self,
request, *args, **kwargs)
|
|
|
|
|
|
decorators in
views.py
|
|
django.contrib.auth.decorators
|
allauth.account.decorators
|
|
|
|
from
rest_framework.decorators import
detail_route
class MyModelViewSet(viewsets.ModelViewSet):
...
@detail_route(permission_classes=[IsAdminOrIsSelf])
def my_function(self,
request, **kwargs):
|
|
|
|
|
|
authentication
views in
views.py |
|
|
Profile
view
|
|
|
|
|
|
|
|
|
from
allauth.socialaccount.providers.facebook.views
import FacebookOAuth2Adapter
from rest_auth.registration.views import
SocialLoginView
class FacebookLogin(SocialLoginView):
adapter_class =
FacebookOAuth2Adapter
...
|
urls.py /
routing.py
|
|
- url('^',
include('django.contrib.auth.urls'))
|
url(r'^accounts/',
include('allauth.urls')),
url(r'^accounts/profile/',
YOUR_OWN_PROFILE_VIEW),
|
|
from
channels.auth import AuthMiddlewareStack
application = ProtocolTypeRouter({
"websocket": AuthMiddlewareStack(
URLRouter([
url(r"^front(end)/$",
consumers.AsyncChatConsumer),
])
),
})
|
|
|
- url(r'^api-token-auth/',
'rest_framework_jwt.views.obtain_jwt_token')
- url(r'^api-token-verify/',
'rest_framework_jwt.views.verify_jwt_token')
- url(r'^api-token-refresh/',
'rest_framework_jwt.views.refresh_jwt_token')
|
from rest_framework_simplejwt.views import
(
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView,
)
urlpatterns = [
...
path('api/token/',
TokenObtainPairView.as_view(),
name='token_obtain_pair'),
path('api/token/refresh/',
TokenRefreshView.as_view(),
name='token_refresh'),
path('api/token/verify/',
TokenVerifyView.as_view(),
name='token_verify'),
...
] |
url(r'^rest-auth/',
include('rest_auth.urls'))
url(r'^rest-auth/',
include('dj_rest_auth.urls'))
# needed by rest-auth/password/reset/
#
https://github.com/Tivix/django-rest-auth/issues/63
url(r'^',
include('django.contrib.auth.urls')),
|
(r'^rest-auth/registration/',
include('rest_auth.registration.urls'))
(r'^rest-auth/registration/',
include('dj_rest_auth.registration.urls'))
|
url(r'^rest-auth/facebook/$',
FacebookLogin.as_view(),
name='fb_login')
...
|
templates
|
|
- registration/login.html
- ...
|
|
|
|
|
|
|
|
|
|
|
entry points
|
|
http://127.0.0.1:8000
- /login
- /logout
- /password_change
- /password_reset
- /password_reset/done
- /reset
- /reset/done
|
http://127.0.0.1:8000/accounts
- /signup
- /login
- /logout
- /password/change
- /password/set
- /inactive
- /email
- /confirm-email
- /password/reset
- /password/reset/done
- /password/reset/key
- /password/reset/key/done
|
http://127.0.0.1:8000/accounts
- /social
- /twitter
- /facebook
- ...
|
|
|
|
|
|
http://127.0.0.1:8000/rest-auth
- /login
- /logout
- /password/reset
- /password/reset/confirm
- /password/change
- /user
|
http://127.0.0.1:8000/rest-auth
- /registration
- /registration/verify-email
|
http://127.0.0.1:8000/rest-auth
|
usage with requests
|
|
|
|
|
|
import
requests
import json
from pprint import pprint
server_url = 'http://127.0.0.1:8000'
# get something with basic authentication
r = requests.get('%s/v1/totos/' %
server_url, ... )
pprint( r.json() )
|
|
import
requests
import json
from pprint import pprint
server_url = 'http://127.0.0.1:8000'
# login
password = open('user1_p.txt',
'r').read().strip()
payload_credentials = {
'username':
'user1',
'password':
password,
}
r = requests.post(server_url+'/api-token-auth/',
data=payload_credentials)
pprint( r.json() )
token = r.json()['token']
# get something
r = requests.get('%s/v1/totos/' %
server_url, headers={'Authorization':'JWT
%s'%token} )
pprint( r.json() )
|
|
import
requests
import json
from pprint import pprint
server_url = 'http://127.0.0.1:8000'
# login
password = open('user1_p.txt',
'r').read().strip()
payload_credentials = {
'username':
'user1',
'password':
password,
}
r = requests.post(server_url+'/rest-auth/login/',
data=payload_credentials)
pprint( r.json() )
key = r.json()['key']
# get something (e.g. user info)
r =
requests.get(backend_url+'/rest-auth/user/',
headers={'Authorization':'Token
%s'%key})
pprint( r.json() )
|
|
|
info
|
|
|
|
|
|
- each call (if required) must contain
username/password
|
|
- first call to api-token-auth to get a
token
- next calls to your api must contain
the token
- token is not stored in database
|
- first call to /api/token to get a jwt
token
token=$(echo $response | jq
-r '.access')
- next calls to your api must contain
the jwt token:
- token is not stored in database
|
- first call to rest-auth/login to get a
token
- next calls to your api must contain
the token:
- /user gives information about the
current user
- Update
UserProfile
- token is stored in database
|
- registration of new users
- implements a user viewset for you
|
|
|
|
authentication
|
authentication
|
social
authentication
|
|
authentication
|
|
|
|
authentication
|
registration
|
social
authentication
|
|
|
default
|
django-allauth
|
|
(default)
|
|
djangorestframework-jwt |
djangorestframework-simplejwt |
django-rest-auth |
|
|
Django
|
|
django-rest-framework
authentication |
- Using
the
Django authentication system
- Authentication
backends
AUTHENTICATION_BACKENDS
- used by:
- complete example:
- settings.py
AUTHENTICATION_BACKENDS
= (
# django-axes
must be the first one
'axes.backends.AxesModelBackend',
# default;
still needed to login by username
in Django admin, regardless of
`allauth`
'django.contrib.auth.backends.ModelBackend',
# `allauth`
specific authentication methods,
such as login by e-mail
'allauth.account.auth_backends.AuthenticationBackend',
# custom
'my_app.auth_backends.MyAuthenticationBackend'
)
- my_app/models.py
class
MyAuthenticationBackend(object):
def
authenticate(self, request,
**credentials):
...
- Customizing authentication in Django
- Authentication execution stack
-
1st stage: check for headers |
2nd stage: view |
|
|
login view |
regular views |
REST_FRAMEWORK.
DEFAULT_AUTHENTICATION_CLASSES
- ...
- 'rest_framework.authentication.SessionAuthentication'
- 'rest_framework.authentication.BasicAuthentication'
- 'rest_framework.authentication.TokenAuthentication',
- 'rest_framework_simplejwt.authentication.JWTAuthentication'
|
AUTHENTICATION_BACKENDS
- # django-axes must be the first
one
'axes.backends.AxesModelBackend',
- # default; still needed to login
by username in Django admin,
regardless of `allauth`
'django.contrib.auth.backends.ModelBackend',
- # `allauth` specific
authentication methods, such as
login by e-mail
'allauth.account.auth_backends.AuthenticationBackend',
- # custom (e.g. to login using an
external service)
'my_app.auth_backends.MyAuthenticationBackend'
|
|
- login view: POST /rest-auth/login
- rest_framework.views.APIView.dispatch
- self.initial
- self.perform_authentication
- rest_framework.request.user
- self._authenticate
- for authentication in
self.authenticators
(BasicAuthentication,
TokenAuthentication,
...)
- ...
- TokenAuthentication.authenticate
- ...
- self.check_permissions
- self.check_throttles
- response = handler(request, *args,
**kwargs)
- dj_rest_auth.views.LoginView.post()
- serializer.is_valid()
- rest_auth.serializers.LoginSerializer.authenticate()
- django.contrib.auth.authenticate()
(for backend: user =
_authenticate_with_backend(backend,
...), # list is defined
by
AUTHENTICATION_BACKENDS )
- axes.backends.AxesModelBackend.authenticate()
- django.contrib.auth.backends.ModelBackend.authenticate()
- allauth.account.auth_backends.AuthenticationBackend.authenticate()
- my_app.auth_backends.MyAuthenticationBackend.authenticate()
- ...
- login()
- self.user =
self.serializer.validated_data['user']
- self.token =
create_token() (by default,
calls
default_create_token(); but
it can be overwritten at
settings.REST_AUTH_TOKEN_CREATOR)
- rest_auth.utils.default_create_token()
- token, _ =
token_model.objects.get_or_create(user=user)
- self.process_login()
- django_login()
- django.contrib.auth.login()
- request.session.cycle_key()
- get_response()
- ...
- protected view: GET, POST ... -H
'Authorization: Token xxx' /my/endpoint
- rest_framework.views.APIView.dispatch
- self.initial
- response = handler(request, ...)
- ...
- ...
- ...
- Creating
superusers
- Multi-site
- Protecció addicional / Additional protection
- Validació de la contrasenya / Password
validation
- Límit del nombre de reintents / Limit login
retries
- django-axes
- 4.5.4: Python 2.7
pip install
django-appconf==1.0.3
django-ipware==2.1.0
django-axes==4.5.4
- docs
- settings.py
INSTALLED_APPS
= [
...
# anywhere
'axes',
...
]
AUTHENTICATION_BACKENDS
= [
#
AxesBackend should be the
first backend in the
AUTHENTICATION_BACKENDS list.
'axes.backends.AxesBackend',
# Django
ModelBackend is the default
authentication backend.
'django.contrib.auth.backends.ModelBackend',
]
- #
when using AWS ELB (reverse
proxies)
AXES_META_PRECEDENCE_ORDER
= [
'HTTP_X_FORWARDED_FOR',
'REMOTE_ADDR',
]
- python.py
manage migrate
- with allauth
- with django-rest-auth
- use version
django-rest-auth>=0.9.4
- previous versions have the
problem:
- login variable must be set at
serializer (as its equivalent
for allauth in forms.py), so as
username is not null in access
attempts (e.g. from admin)
- myproject/settings.py
AXES_USERNAME_FORM_FIELD
= 'login'
REST_AUTH_SERIALIZERS
= {
...
'LOGIN_SERIALIZER':
'myapp.serializers.MyLoginSerializer',
}
- myapp/serializers.py
from
rest_framework import
exceptions
from
rest_auth.serializers
import LoginSerializer
class
MyLoginSerializer(LoginSerializer):
#
NOTE: this serializer
is partially
overwritten (and
referenced in
settings.REST_AUTH_SERIALIZERS['LOGIN_SERIALIZER'])
#
to add key "login",
defined in
settings.AXES_USERNAME_FORM_FIELD,
so as username in
AccessAttempt is
filled correctly.
#
Inspired by https://django-axes.readthedocs.io/en/latest/6_integration.html#integration-with-django-allauth,
where
#
the same mechanism is
implemented in forms
(equivalent for
serializer).
def
_validate_email(self,
email, password):
user = None
if email and password:
user =
self.authenticate(email=email,
password=password,
login=email)
else:
msg = _('Must include
"email" and
"password".')
raise
exceptions.ValidationError(msg)
return user
def
_validate_username(self,
username, password):
user = None
if username and
password:
user =
self.authenticate(username=username,
password=password,
login=username)
else:
msg = _('Must include
"username" and
"password".')
raise
exceptions.ValidationError(msg)
return user
def
_validate_username_email(self,
username, email,
password):
user = None
if email and password:
user =
self.authenticate(email=email,
password=password, login=email)
elif username and
password:
user =
self.authenticate(username=username,
password=password, login=username)
else:
msg = _('Must include
either "username" or
"email" and
"password".')
raise
exceptions.ValidationError(msg)
return user
- with django restframework
- ...
- 5.x: Python 3, Django 2
- Problemes / Problems
- django.core.exceptions.ImproperlyConfigured:
django-axes does not work properly
with LocMemCache as the default
cache backend please add e.g. a
DummyCache backend for axes and
configure it with AXES_CACHE
- Solució / Solution
- settings.py
CACHES
= {
'axes': {
'BACKEND':
'django.core.cache.backends.dummy.DummyCache',
}
}
AXES_CACHE = 'axes'
class
AppConf(metaclass=AppConfMetaClass):
^
SyntaxError: invalid syntax
- Customizing
authentication
in Django (1.5)
- Extending
the
existing User model / Substituting
a
custom User model
- Use cases
- use case 1: additional fields, not
used to authenticate (profiles)
- Properly
Admin’ing
User Profile Models
- IntegrityError in admin solved
- Django < 1.5
- my_app/models.py
from
django.contrib.auth.models
import User
class
UserProfile(models.Model):
user =
models.OneToOneField(User)
department
=
models.CharField(max_length=100)
from
django.db.models.signals
import post_save
from
django.dispatch import
receiver
from django.db import
transaction
@receiver(post_save,
sender=User)
def
create_user_profile(sender,
instance, **kwargs):
"""
Create a
profile when a user is
created.
"""
# do not
create a userprofile when
coming from loaddata
# TODO:
consider check of
kwargs.get('created', True)
#
(http://stackoverflow.com/questions/3499791/how-do-i-prevent-fixtures-from-conflicting-with-django-post-save-signal-code)
if not
kwargs.get('raw', False):
try:
with
transaction.atomic():
user_profile =
UserProfile.objects.get_or_create(user=instance)
except Exception as e:
print
e
- my_app/admin.py
from
django.contrib import admin
from django.contrib.auth.admin
import UserAdmin
from
django.contrib.auth.models
import User
from django.contrib.auth
import get_user_model
from
my_user_profile_app.models
import UserProfile
# Define an inline admin
descriptor for UserProfile
model
# which acts a bit like a
singleton
class
UserProfileInline(admin.StackedInline):
model =
UserProfile
can_delete
= False
verbose_name_plural =
'employee'
# Define a new User admin
class
CustomUserAdmin(UserAdmin):
#inlines
= (UserProfileInline, )
list_display =
('email','username','department',)
search_fields = ('username',
'first_name', 'last_name',
'email','userprofile__department',)
# used by
list view
def
department(self,item):
return
item.userprofile.department
# used by
detail view
def
change_view(self, request,
object_id, form_url='',
extra_context=None):
self.inlines
= (UserProfileInline,)
return super(UserAdmin,
self).change_view(
request,
object_id, form_url=form_url,
extra_context=extra_context)
# Re-register UserAdmin
admin.site.unregister(get_user_model())
admin.site.register(get_user_model(),
CustomUserAdmin)
- use case 2: authentication must be
done using a field different from
"username"
- use case 3: authentication must be
done using a new field ('dni')
- Django < 1.5
- Django >= 1.5
- Notes:
- not supported by
makemigrations
- not suited for reusable
applications
- settings.py
AUTH_USER_MODEL
= 'my_app.MyUser'
- my_app/models.py
from
django.contrib.auth.models
import AbstractUser
class MyUser(AbstractUser):
dni =
models.CharField("DNI",
max_length=9, unique=True)
class
Meta:
db_table = 'auth_user'
verbose_name = _('MyUser')
verbose_name_plural =
_('MyUsers')
- in your other py files that
reference User:
from
django.conf import settings
#user =
models.ForeignKey(User)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
- or add (to avoid error message: "
Manager
isn't available; User has been
swapped for... " when
accessing to admin):
from
django.contrib.auth import
get_user_model
User = get_user_model()
- my_app/admin.py
(per a poder gestionar les
contrasenyes i els camps addicionals
/ to deal with passwords and added
fields)
- views.login
- Curso
Django:
gestión de usuarios
- Authenticating
against
Django’s user database from Apache
- Sessions
- Multi-/Two-factor authentication (MFA)
- External authentication (other authentication sources)
- Social authentication
- Signing
up
and signing in
- What's
the
best solution for OpenID with Django? [closed]
- django-allauth
- Readthedocs
- Can be used as:
- extension for default authentication
- social authentication
- Django-allauth
Tutorial
- The
missing
django-allauth tutorial
- Signing
Up and Signing In: Users in Django with
Django-AllAuth
- Advanced
usage
- Templates
- lib/python2.7/site-packages/allauth/templates/account/email/
my_app/templates/account/email/
- email_confirmation_subject.txt
- email_confirmation_signup_subject.txt
- password_reset_key_subject.txt
- email_confirmation_message.txt
- email_confirmation_signup_message.txt
- password_reset_key_message.txt
- i18n
- lib/python2.7/site-packages/django/contrib/admin/locale/ca/LC_MESSAGES/django.po
- lib/python2.7/site-packages/allauth/locale/xx/
- Custom url
- Custom account adapter to generate an
external url to confirm email address:
- my_project/settings.py
ACCOUNT_ADAPTER
=
'my_app.adapter.MyAccountAdapter'
# url to appear on email message
to verify email address
CONFIRM_EMAIL_URL_TEMPLATE="http://www.toto.org/confirm-email/{key}/"
- my_app/adapter.py
from
django.conf import settings
from allauth.account.adapter
import DefaultAccountAdapter
class
MyAccountAdapter(DefaultAccountAdapter):
def get_email_confirmation_url(self,
request,
emailconfirmation):
absolute_url =
settings.CONFIRM_EMAIL_URL_TEMPLATE.format(key=emailconfirmation.key)
return absolute_url
http://www.toto.org/confirm-email/<received_key>/
- when using django-rest-auth,
this page must send a POST to API
REST server. Curl equivalent:
curl -X POST -H
'Content-Type:
multipart/form-data' -F
key=${received_key}
${backend_url}/rest-auth/registration/verify-email/
- Custom reset password email message
- Delete account
- With django-rest-framework
- Plug
in
django-allauth as endpoint in
django-rest-framework
- dj-rest-auth
- replacement for django-rest-auth
- Python 3.5
- Django 2, 3
- For Python 3 and Django 1.11:
pip install
"dj-rest-auth=1.1.0"
- For Python 2.7 and Django 1.11:
- pip
install "dj-rest-auth==0.1.1"
- will not work, as utils.py
does not implement
import_callable
- pip install -e
"git://github.com/iMerica/dj-rest-auth.git@0.1.4#egg=dj-rest-auth"
- will not work, because it
requires Python>=3.5
- django-rest-auth
- IMPORTANT: use dj-rest-auth
instead
- Documentation
- FAQ
- How
can I update UserProfile assigned to
User model?
- my_app/serializers.py
from
rest_auth.serializers
import
UserDetailsSerializer
class
UserSerializer(UserDetailsSerializer):
company_name =
serializers.CharField(
source='userprofile.screen_name'
)
def
update(self, instance,
validated_data):
profile_data =
validated_data.pop('userprofile',
{})
instance =
super(UserSerializer,
self).update(instance,
validated_data)
# get and update user
profile
profile =
instance.userprofile
for attr, value in
profile_data.items():
setattr(profile, attr,
value)
profile.save()
return instance
class
Meta(UserDetailsSerializer.Meta):
fields =
UserDetailsSerializer.Meta.fields
+ ('company_name',)
- settings.py
REST_AUTH_SERIALIZERS
= {
'USER_DETAILS_SERIALIZER':
'my_app.serializers.UserSerializer'
}
- Installation
- pip install django-rest-auth
- AngularJS
- Problemes / Problems
- NoReverseMatch at
/rest-auth/password/reset/
- registration (login?) not working
with Django devel server
(runserver). Is "@" from email a
problem with curl when using
multipart?
- do not use Content-Type:
multipart/form-data:
#response=$(curl -X
POST -H 'Content-Type:
multipart/form-data' -F
username=$username -F
password1=$password -F
password2=$password -F
email=$email
${backend_url}/rest-auth/registration/
)
- do use Content-Type:
application/json:
response=$(curl -X
POST -H 'Content-Type:
application/json'
--data-binary
"{\"username\":\"$username\",\"email\":\"$email\",\"password1\":\"$password\",\"password2\":\"$password\"}"
${backend_url}/rest-auth/registration/
)
- Identity providers
- Autenticació
amb django-rest-framework / Authentication with
django-rest-framework
- how to register users in
django-rest-framework?
- djangorestframework
- settings.py
-
|
REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] |
authentication
is needed for everything
|
'rest_framework.permissions.IsAuthenticated' |
authentication
is needed when changing values
|
'rest_framework.permissions.IsAuthenticatedOrReadOnly' |
- ViewSet
class
ParticipantChallengeViewSet(viewsets.ModelViewSet):
...
permission_classes =
(permissions.IsAuthenticated,)
- Recupera i utilitza l'usuari des
de la petició / Get and use user from request
-
|
admin
|
djangorestframework
|
|
admin.py
|
views.py
|
serializers.py
|
input
|
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
exclude = ('owner',)
# only objects
that belong to user
def has_change_permission(self,
request,
obj=None):
has_class_permission = super(MyModelAdmin,
self).has_change_permission(request, obj)
if not
has_class_permission:
return
False
if obj
is not None and not request.user.is_superuser and
request.user.id != obj.owner.id:
return
False
return
True
# automatically get the owner
from request user
def save_model(self,
request, obj, form, change):
if not
change:
obj.owner
= request.user
obj.save() |
from
rest_framework import permissions
class MyModelViewSet(viewsets.ModelViewSet):
queryset =
MyModel.objects.all()
serializer_class =
MyModelSerializer
permission_classes =
(permissions.IsAuthenticated,)
# automatically get the owner
from request user
# (pre_save has been deprecated
in DRF 3.x)
def perform_create(self,
serializer):
#
Include the owner attribute directly, rather than
from request data.
instance =
serializer.save(owner=self.request.user) |
class
MyModelSerializer(serializers.ModelSerializer):
def __init__(self, *args,
**kwargs):
super(MyModelSerializer, self).__init__(*args,
**kwargs)
if
kwargs.get('context', None):
request
= kwargs['context'].get('request', None)
self.user
= request.user
# Note: Nested
serializer does not receive view context #2555
class ChildSerializer(serializers.Serializer):
...
class ParentSerializer(serializers.Serializer):
# child = ChildSerializer(many=True)
def __init__(self, *args,
**kwargs):
super( ParentSerializer,self ).__init__(*args,
**kwargs)
self.fields['child'] =
ChildSerializer(many=True,context=self.context) |
|
|
Consider also using a
filter: use custom
filter backend
|
|
output
|
@admin.register(MyModel)
class
MyModelAdmin(admin.ModelAdmin):
exclude = ('owner',)
# only objects that belong to
user
# Django < 1.6
(for 1.6 use get_queryset instead)
# def
queryset(self,
request):
#
if request.user.is_superuser:
#
return
MyModel.objects.all()
#
return
MyModel.objects.filter(usuari=request.user)
# Django 1.6 :
queryset has been renamed to get_queryset
def get_queryset(self,
request):
qs =
super(MyModelAdmin, self).get_queryset(request)
if
request.user.is_superuser:
return
qs
return
qs.filter(owner=request.user) |
from
rest_framework import permissions
class MyModelViewSet(viewsets.ModelViewSet):
queryset =
MyModel.objects.all()
serializer_class =
MyModelSerializer
permission_classes =
(permissions.IsAuthenticated,)
# only objects that belong to
user
def get_queryset(self):
return
MyModel.objects.filter(owner=self.request.user) |
|
- Exemple 1 (inside directory my_app)
- urls.py
url(r'^nou_usuari/$', 'nou_usuari'),
url(r'^entrada/$', 'entrada'),
url(r'^zona_privada/$',
'zona_privada'),
url(r'^sortida/$', 'sortida'),
- views.py (Working
with
forms), using
django.contrib.auth.forms:
from django.contrib.auth.forms
import UserCreationForm
from django.contrib.auth.forms import
AuthenticationForm
from django.contrib.auth import login, authenticate,
logout
from django.contrib.auth.decorators import
login_required
from django.template import RequestContext
def nou_usuari(request):
if request.method=='POST':
formulari =
UserCreationForm(request.POST)
if
formulari.is_valid:
formulari.save()
return
HttpResponseRedirect('/')
else:
formulari =
UserCreationForm()
return render_to_response('nou_usuari.html',{'formulari':formulari},
context_instance=RequestContext(request))
def entrada(request):
if not request.user.is_anonymous():
return
HttpResponseRedirect('/zona_privada')
if request.method == 'POST':
formulari =
AuthenticationForm(request.POST)
if
formulari.is_valid:
usuari
= request.POST['username']
contrasenya
= request.POST['password']
compte
= authenticate(username=usuari,
password=contrasenya)
if acces is not None:
if
compte.is_active:
login(request,compte)
return
HttpResponseRedirect('/zona_privada')
else:
return
render_to_response('compte_no_actiu.html',
context_instance=RequestContext(request))
else:
return
render_to_response('usuari_incorrecte.html',
context_instance=RequestContext(request))
else:
formulari =
AuthenticationForm()
return render_to_response('entrada.html',{'formulari':formulari},
context_instance=RequestContext(request))
@login_required(login_url='/entrada')
def zona_privada(request):
usuari = request.user
return render_to_response('zona_privada.html',
{'usuari':usuari},
context_instance=RequestContext(request))
@login_required(login_url='/entrada')
def sortida(request):
logout(request)
return HttpResponseRedirect('/')
- templates/
- nou_usuari.html
<h1>Registra nou
usuari</h1>
<form id='formulari' method='post'
action=''>
{% csrf_token %}
<table border=1>{{formulari}}</table>
<p><input
type='submit' value='Crea'/></p>
</form>
- compte_no_actiu.html
<h1>Aquest compte no
està actiu</h1>
- usuari_incorrecte.html
<h1>Usuari
incorrecte</h1>
- entrada.html
<h1>Entrada</h1>
<form id='formulari' method='post'
action=''>
{% csrf_token %}
<table border=1>{{formulari}}</table>
<p><input
type='submit' value='Entrada'/></p>
</form>
- zona_privada.html
<p>Benvingut {{usuari.username|upper}}
<p>Membre des de {{usuari.date_joined}}
- Exemple 2 (root directory)
- urls.py (using
django.contrib.auth.views )
import
django.contrib.auth.views
...
url(r'accounts/login/$', 'django.contrib.auth.views.login'),
...
- my_app/templates/
- si funciona, anirà a accounts/profile (LOGIN_REDIRECT_URL)
- Exemple 2 (root directory)
- urls.py (using
django.contrib.auth.views )
import
django.contrib.auth.views
...
url(r'accounts/login/$', 'django.contrib.auth.views.login',
{'template_name':
'login2.html'}),
...
- my_app/templates/
- si funciona, anirà a accounts/profile
|
Admin
|
- The Django admin site (1.6)
(1.7)
- Django
admin
snippets
- Polls example admin
- Registre / Register
admin.site.register( TotoModel )
- Django >= 1.7
@admin.register(TotoModel)
class TotoModelAdmin(admin.ModelAdmin):
## list
# default:
#list_display = ("__str__",)
# non-default:
list_display
= ("field_1", "field_2", "date_field_10",)
#
list_display_links = ("field_2",)
# filter by fields (filter area on
right):
list_filter
= ("field_1",)
# searchable fields (search on
top):
search_fields = ("field_2",)
# dates (on top):
date_hierarchy
= "date_field_10"
## detail
# non-grouped fields:
#fields = ("field_1", "field_2",)
# fields grouped in a single line:
#fields = ["
field_1 ",
" field_2 ", (" field_3 ",
" field_4 "), ( "field_5 ",
" field_6 "), "field_7"]
# fields grouped:
fieldsets
= [
(None,
{"fields": ["
field_1
",
" field_2
"],
"classes": ["wide"], "description": "This group is
cool"}),
(_("Group
A"), {"fields": [" field_3 ",
" field_4 "], "classes":
["wide"]}),
(_("Group
B"), {"fields": [(" field_5a ",
" field_5b "), "field_6"],
"classes": ["wide","collapse"]}),
(None,
{"fields": [" field_ 7"],
"classes": ["wide"]}),
]
# prepopulate SlugFields:
prepopulated_fields
= {"slug_field_1": ("field_1",)}
# non-editable fields:
readonly_fields = ("field_3",)
# exluded fields (when not
specifying fields):
#exclude = ("field_8",)
# many2many:
filter_horizontal
= ("m2m_field_20",)
filter_vertical
= ("m2m_field_21",)
# foreign keys to another model
(can perform search in a pop-up window):
raw_id_fields = ("fk_field_20",)
# foreign keys from another model:
inlines =
[ModelAInline,ModelBInline,]
...
# allow save as new
save_as = True
- Django < 1.7
class
TotoModelAdmin(admin.ModelAdmin):
...
admin.site.register( TotoModel, TotoModelAdmin )
- Desregistre / Unregister
- my_project/urls.py
from django.contrib import
admin
from django.contrib.auth.models import User, Group
admin.site.unregister(User)
admin.site.unregister(Group)
- Fieldsets
- classes
- default
- wide, collapsed
- lib/python2.7/site-packages/django/contrib/admin/static/admin/css/forms.css
- /* WIDE FIELDSETS */
- /* COLLAPSED FIELDSETS */
- custom widget:
- mysite/myapp/admin.py
from
django.contrib.gis.db import models
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides
= {
models.TextField: {"widget":
widgets.Textarea(attrs={"rows": "1"})},
}
- custom css:
- How
to adjust grid in Django's fieldset.html
- mysite/myproject/settings.py
# additional source
for statics
STATICFILES_DIRS = (
os.path.join(BASE_DIR,
"static_general"),
)
- mysite/myapp/admin.py
class
StackDefaultsAdmin(SingletonModelAdmin):
# detail
fieldsets = [
(_('My Group'), {'fields': ['myfield',],
'classes': ['wide','extra_wide_field']}),
]
# css for class
extra_wide_field
class Media:
css
= {
'all': ('admin/css/my.css',)
}
- mysite/static_general/admin/css/my.css
/* EXTRA WIDE
FIELDSETS */
.extra_wide_field input.vTextField {
width: 40em;
}
- ...
- Llista / List
- list_display
- Nombre d'elements per pàgina / Number of item per page
- Dates amb més precisió / Dates with more precision
- Date
Format in Django Admin
- settings.py
# get more precision
in admin list_display dates
precise_date_format = "d b Y H:i:s"
from django.conf.locale.ca import formats as
ca_formats
ca_formats.DATETIME_FORMAT =
precise_date_format
from django.conf.locale.en import formats as
en_formats
en_formats.DATETIME_FORMAT =
precise_date_format
from django.conf.locale.es import formats as
es_formats
es_formats.DATETIME_FORMAT =
precise_date_format
from django.conf.locale.fr import formats as
fr_formats
fr_formats.DATETIME_FORMAT =
precise_date_format
- html per als valors d'una columna
list_display = ["my_col"]
def my_col(self, obj):
return obj.myfield
my_col.short_description = "My column"
my_col.allow_tags = True
list_display = ["my_col"]
def my_col(self, obj):
target = getattr(obj,
"my_url_field")
if target:
return
'<a href="%s">%s</a>' % (target,
target)
else:
return
None
my_col.short_description = "My column"
my_col.allow_tags = True
- Mostra
només
els objectes de l'usuari / Show only objects of user
- Afegeix
columnes
/ Add columns
- ManyToManyField
- ForeignKey
- list_filter
- Detall / Detail
- Groups
- no groups
fields =
['first','second', 'third_first', 'third_second',
'fourth_first', 'fourth_second', 'fifth']
- in a single line
fields =
['first','second', ('third_first',
'third_second'), ('fourth_first',
'fourth_second'), 'fifth']
- in a section
fieldsets = [
(None, {'fields':
['first','second'], 'classes': ['wide']}),
(_('Third'), {'fields': ['third_first',
'third_second'], 'classes': ['wide']}),
(_('Fourth'), {'fields': ['fourth_first',
'fourth_second'], 'classes': ['wide']}),
(None,
{'fields': ['fifth'], 'classes': ['wide']}),
]
- Inline
- Simple examples
- admin.py
from django.contrib
import admin
#class MembershipInline(admin.StackedInline):
class MembershipInline(admin.TabularInline):
model = Membership
@admin.register(Group)
class GroupAdmin(admin.ModelAdmin):
model = Group
inlines =
[MembershipInline,]
- Link to modify object
- How
to
limit django admin inline formsets
- How do I require an inline
in the Django Admin?
- forms.py
- admin.py
class
TotoInline(admin.StackedInline)
...
formset =
RequiredInlineFormSet
- exactly 2 objects:
class
TotoInline(admin.StackedInline)
...
max_num = 2
extra = 2
- Conditional inlines
- Django admin inline
conditional values
- Filtering
an
inline's dropdown based on the inline instance
- with two-step:
@admin.register(Stream)
class StreamAdmin(admin.ModelAdmin):
model = Stream
def
get_inline_instances(self, request, obj=None):
inlines
= []
if obj!=None:
if (obj.stream_type
== 2) or (obj.stream_type == 27): # video: 2,
27
inlines
= [VideoStreamInline]
elif (obj.stream_type == 4) or
(obj.stream_type == 17): # audio: 4, 17
inlines
= [AudioStreamInline]
return [inline(self.model, self.admin_site)
for inline in inlines]
def response_add(self,
request,
obj, post_url_continue=None):
if
'_addanother' not in request.POST and '_popup'
not in request.POST:
request.POST['_continue']
= 1
return super(StreamAdmin,
self).response_add(request, obj,
post_url_continue)
- Nested inlines
- django-nested-inline
pip install -e
git+git://github.com/s-block/django-nested-inline.git#egg=django-nested-inline
- bugs:
- in order to use overwritten
get_inline_instances (see above: conditional
inlines), the following files has to
be modified:
- <virtualenv_path>/src/django-nested-inline/nested_inline/static/admin/js/inlines-nested.js
(to
avoid bug of having the same inline as
the first element, when doing "Add
another...")
- not working with validate_unique
- Multiple fk
- when several fk exist from inlined object to
present one, you must choose one:
- List of links to fk objects instead of objects
themselves
- List of links to m2m
objects instead of objects themselves
class
ClassroomPersonInline(admin.TabularInline):
model =
Classroom.members.through
verbose_name = _("Membership")
verbose_name_plural =
_("Memberships")
raw_id_fields = ("person",)
- Problemes / Problems
- Delete
- Overriding
predefined model methods
- "Overridden model methods are not called on bulk
operations"
- Solucions / Solutions
- models.py
- use pre_delete, post_delete (cannot be
interrupted)
- delete method is not used by bulk delete
(default action in list display)
- admin.py
- Two-step (as in user creation:
edit the just added object)
- Accions:
pàgina de confirmació / Actions: confirmation page
- Filter by date
- use datetime admin widget
- In Django, how do I mimic the
two-step method of adding users through the admin for my
own models?
class
StreamAdmin(admin.ModelAdmin):
# fieldsets for first step (add new
object)
add_fieldsets = (
(None, {
'classes':
('wide',),
'fields':
('username', 'password1', 'password2'),
}),
)
# fieldsets for second step in add
object; default for modifying an object
fieldsets = (
(None,
{'fields': ('username', 'password')}),
(_('Personal
info'), {'fields': ('first_name', 'last_name', 'email')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff',
'is_superuser',
'groups',
'user_permissions')}),
(_('Important
dates'), {'fields': ('last_login', 'date_joined')}),
)
def get_fieldsets(self, request,
obj=None):
if not obj:
return
self.add_fieldsets
return
super(UserAdmin, self).get_fieldsets(request, obj)
def response_add(self,
request,
obj, post_url_continue=None):
if
'_addanother' not in request.POST and '_popup' not in
request.POST:
request.POST['_continue']
= 1
return
super(StreamAdmin, self).response_add(request, obj,
post_url_continue)
- Extensions
- How to add report section to
the Django admin?
- Three
awesome
Django admin enhancements
- Bulk edit
- django-mass-edit
- Instal·lació / Installation
pip install django-mass-edit
- Configuració / Setup
- my_project/my_project/settings.py
INSTALLED_APPS = (
...
'massadmin',
...
- my_project/my_project/urls.py
urlpatterns = [
url(r'^admin/',
include(admin.site.urls)),
url(r'^admin/',
include('massadmin.urls')),
...
- Usage
- django-adminplus
- Instal·lació / Installation
pip install django-adminplus
pip install -e
git://github.com/jsocol/django-adminplus#egg=django-adminplus
- Utilització / Usage
- my_project/my_project/settings.py
INSTALLED_APPS = (
#'django.contrib.admin',
'django.contrib.admin.apps.SimpleAdminConfig',
'adminplus',
)
- my_project/urls.py
from adminplus.sites
import AdminSitePlus
admin.site = AdminSitePlus()
admin.autodiscover()
- my_project/my_app/admin.py
@admin.site.register_view('my_view',_("View
to
do something"))
def my_view(request, *args, **kwargs):
# do something
# redirect to admin home
url =
reverse('admin:index')
return
HttpResponseRedirect(url)
- Problemes / Problems
Could not parse the remainder: ':index' from
'admin:index'. The syntax of 'url' changed in
Django 1.5, see the docs.
- Solució / Solution:
- modify file
/your_python_path_or_virtualenv//src/django-adminplus/adminplus/templates/adminplus/base.html
- <a href="{% url 'admin:index'
%}">Home</a> › {{
title }}
- Grappelli
- Adminactions
- import-export
- CKEditor
- django-admin-csvexport
- ...
- Problemes
- 2.2: només es poden seleccionar els camps si l'usuari
té permisos d'administració
- Personalizació /
Customization
- AdminSite
(1.7)
- my_project/urls.py
from
django.utils.translation import ugettext_lazy as _
admin.site.site_header = _('My site_header') #
"Django administration"
admin.site.site_title = _( 'My
site_title') # "Django site admin"
admin.site.index_title = _( 'My
index_title') # "Site administration"
- Custom ordering
- Overriding
admin
templates
- AdminSite
attributes (dev version)
- Customizing
the
Django Admin (slides)
- Reverse admin
- Extra
context (e.g. a url for a button in a detail page)
- admin.py
class
MyModelAdmin(admin.ModelAdmin):
def change_view(self,
request, object_id, form_url="",
extra_context=None):
#
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.changelist_view
extra_context = extra_context or {}
#
complete context with my_url
device_stats_url = ""
obj =
self.get_object(request, object_id)
extra_context["my_url"] = obj...
return
super().change_view(
request,
object_id,
form_url,
extra_context=extra_context,
)
- my_project/my_app/templates/admin/my_app/my_model/change_form.html
{% extends
"admin/change_form.html" %}
{% load i18n %}
{% block object-tools-items %}
<li><a href="{{ my_url
}}">{% trans "Go to url button"
%}</a></li>
{{ block.super }}
{% endblock %}
- Plantilles / Templates
- ...
|
|
default |
custom |
|
rendered by |
template |
blocks |
admin.py |
template |
|
|
admin/base_site.html |
- title
- branding
- nav-global
|
|
|
list |
ModelAdmin
|
admin/change_list.html |
- extrastyle
- extrahead
- content
- object-tools
- object-tools-items (add custom
list page buttons
here)
- search
- date_hierarchy
- result_list
- filters
|
class
MyModelAdmin(admin.ModelAdmin):
# list
# no need to specify if
you put html template file in the
canonical dir
change_list_template =
"admin/my_app/my_model/change_list.html"
|
canonical dir:
my_project/my_app/templates/admin/my_app/my_model/change_list.html
{% extends "admin/change_list.html"
%}
{% load i18n %}
{% block object-tools-items %}
<li>...</li>
...
{{ block.super }}
{% endblock %}
|
detail |
ModelAdmin.
|
admin/change_form.html |
- extrastyle
- coltype
- bodyclass
- breadcrumbs
- content
- object-tools
- object-tools-items (add custom
detail page buttons
here)
- ...
|
class
MyModelAdmin(admin.ModelAdmin):
# detail
# no need to specify
if you put html template file in the
canonical dir?
change_form_template =
"admin/my_app/my_model/change_form.html"
# NOTE: we must overwrite
render_change_form instead of simply set
change_form_template because of django-mass-edit
# https://github.com/burke-software/django-mass-edit/issues/82
def
render_change_form(self, *args, **kwargs):
self.change_form_template =
"admin/my_app/my_model/change_form.html"
return super().render_change_form(*args,
**kwargs)
|
canonical dir:
my_project/my_app/templates/admin/my_app/my_model/change_form.html
{% extends "admin/change_form.html"
%}
{% load i18n %}
{% block object-tools-items %}
<li>...</li>
...
{{ block.super }}
{% endblock %}
|
fieldset |
|
|
|
|
|
field inline |
|
admin/edit_inline/stacked.html |
- |
|
|
|
|
admin/edit_inline/tabular.html |
- |
|
|
- Descripcions / Descriptions
|
|
list page |
- add block "description" to your project-wide
template
my_project/templates/admin/change_list.html
{% extends
"admin/change_list.html" %}
{% block content %}
{% block description %}
{% endblock %}
{{ block.super }}
{% endblock %}
- use new block "description" from your app
template in
my_project/my_app/templates/admin/my_app/my_model/change_list.html
(it will extend your project-wide template)
{% extends
"admin/change_list.html" %}
{% load i18n %}
{% block description %}
Description of mymodel
{% endblock %}
|
detail page |
- add block "description" to your project-wide
template
my_project/templates/admin/change_form.html
{% extends
"admin/change_form.html" %}
{% block content %}
{% block description %}
{% endblock %}
{{ block.super }}
{% endblock %}
- use new block "description" from your app
template in
my_project/my_app/templates/admin/my_app/my_model/change_form.html
(it will extend your project-wide template)
{% extends
"admin/change_form.html" %}
{% load i18n %}
{% block description %}
Description of mymodel
{% endblock %}
|
detail fieldset |
fieldsets
= [
(..., "description":
"description for this fieldset")
] |
detail field
|
help from field in models.py: help_text |
inline block |
... |
description or href of an inline block |
- admin.py
class
MyOtherModel(admin.TabularInline):
template =
"admin/my_app/my_other_model_inline/tabular_help_myhelp.html"
- create a new block in template
my_app/templates/admin/edit_inline/tabular_help.html,
based on system admin/edit_inline/tabular.html:
...
<fieldset class="module {{
inline_admin_formset.classes }}">
<h2>{{
inline_admin_formset.opts.verbose_name_plural|capfirst
}}</h2>
{{
inline_admin_formset.formset.non_form_errors
}}
{% block help-region
%}
{% endblock %}
<table>
...
- my_app/templates/admin/edit_inline/tabular_help_myhelp.html
{% extends
"admin/edit_inline/tabular_help.html"
%}
{% load i18n static %}
{% block help-region %}
<a href="{% static 'myhelp.html'
%}">{% trans "Help" %}</a>
{{ block.super }}
{% endblock %}
- my_app/static/myhelp.html
|
- 5
ways to make Django Admin safer
- Change the URL
- Visually distinguish environments
mkdir -p my_project/templates/admin
cp -pr
.../lib/python2.7/site-packages/django/contrib/admin/templates/admin/base_site.htmlmy_project/templates/admin
my_project/templates/admin/base_site.html
{% extends
"admin/base.html" %}
{% block extrastyle %}
<style type="text/css">
body:before {
display: block;
line-height: 35px;
text-align: center;
font-weight: bold;
text-transform: uppercase;
color: yellow;
content: "{{ ENVIRONMENT_NAME }}";
background-color: {{ ENVIRONMENT_COLOUR }};
}
</style>
{% endblock %}
{% block title %}{{ title }} | {{
site_title|default:_('Django site admin') }}{%
endblock %}
{% block branding %}
<h1 id="site-name"><a href="{% url
'admin:index' %}">{{
site_header|default:_('Django administration')
}}</a></h1>
{% endblock %}
{% block nav-global %}{% endblock %}
my_project/context_processors.py
from django.conf
import settings
def from_settings(request):
return {
'ENVIRONMENT_NAME':
settings.DEPLOYMENT_ENVIRONMENT_NAME,
'ENVIRONMENT_COLOUR':
settings.DEPLOYMENT_ENVIRONMENT_COLOUR,
}
my_project/my_project/settings.py
DEPLOYMENT_ENVIRONMENT_NAME
= 'production'
DEPLOYMENT_ENVIRONMENT_COLOUR = 'red'
TEMPLATES = [
{
'BACKEND':
'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,
'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'context_processors.from_settings',
],
},
},
]
- Name your Admin site
- Separate the Django Admin from the main site
- Add Two Factor Authentication (2FA)
- Exemple d'admin
- Customize
the
admin look and feel
- Customize
the
admin index page
- Només un fitxer:
mkdir -p my_project/templates/admin
source env/bin/activate; python -c "import
django; print(django.__path__)"
cp -pr
/opt/PYTHON27/lib/python2.7/site-packages/django/contrib/admin/templates/admin/base_site.html
my_project/templates/admin
- Tota l'estructura de fitxers:
cp -pr
/opt/PYTHON27/lib/python2.7/site-packages/django/contrib/admin/templates
my_project/templates
- modifiqueu, per exemple, base.html
- modifiqueu settings.py:
- TEMPLATE_DIRS
- "if it grew more sophisticated and required
modification of Django’s standard admin
templates for some of its functionality, it
would be more sensible to modify the application’s
templates, rather than those in the project."
TEMPLATE_DIRS = (
# Put strings here, like
"/home/html/django_templates" or
"C:/www/django/templates".
# Always use forward
slashes, even on Windows.
# Don't forget to use
absolute paths, not relative paths.
os.path.join(BASE_DIR,
'templates'),
)
- TEMPLATE_LOADER
- Botó / Button
- Element de la llista / Element in a list
- How
to Add Custom Action Buttons to Django Admin
- Afegir un botó a cada element de la llista,
que porta a una llista d'un altre model, filtrat
per l'id del model actual:
-
admin.py |
from django.utils.html
import format_html
from django.urls import reverse
@admin.register(MyModel)
class
MyModelAdmin(admin.ModelAdmin):
# list
list_display =
(...,"additional_button",...)
def
additional_button(self, item):
mymodel_id = getattr(item, "id")
result = format_html(
'<a class="button"
href="{url}?mymodel__id__exact={id}">Other
model</a>'.format(
url=reverse("admin:my_app_my_model_changelist"),
id=mymodel_id,
)
)
return result
additional_button.short_description
= "Other model"
additional_button.allow_tags =
True |
- Afegir un botó a cada element de la llista,
que retorna un json amb la serialització d'unes
dades addicionals / Add a button to every item
in the list, that returns a json with the
serialization of additional data:
-
admin.py
|
from
django.http import HttpResponse
from django.utils.html import
format_html
from myapp.serializers import
AdditionalSerializer
@admin.register(MyModel)
class
MyModelAdmin(admin.ModelAdmin):
# list
list_display =
(...,'additional_buttons',...)
|
def get_model_info(self):
# module_name is renamed to
model_name in Django 1.8
app_label =
self.model._meta.app_label
try:
return (app_label,
self.model._meta.model_name,)
except AttributeError:
return (app_label,
self.model._meta.module_name,)
def
get_urls(self):
urls = super(MyModelAdmin,
self).get_urls()
info = self.get_model_info()
my_urls = [
url(r'^(?P<uuid>.+)/additional/$',
self.admin_site.admin_view(self.additional_json_view),
name='%s_%s_additional'
% info)
]
return my_urls + urls
def additional_buttons(self,
obj):
return format_html(
'<a class="button"
href="{}">Additional</a>',
reverse('admin:myapp_mymodel_additional',
args=[obj.uuid])
)
additional_buttons.short_description
= 'Additional'
additional_buttons.allow_tags =
True
|
def additional_json_view(self,
request, uuid, *args, **kwargs):
#instance =
self.get_object(request, pk)
# to use uuid field instead of pk
to get instance:
instance =
self.get_object(request, uuid,
from_field='uuid')
# build temporary dict
obj = dict()
obj['my_key'] =
instance.get_my_value()
...
# serialize dict
serializer =
AdditionalSerializer(obj)
return
HttpResponse(json.dumps(serializer.data),
content_type="application/json")
|
- General a la llista (al costat del botó per a
afegir un element) / General in the list (near the
button to add an element)
- Afegir un botó
a la llista, per a pujar
un fitxer, parsejar-lo i desar-ne els
objectes (només una pàgina addicional, amb
formulari) / Add a button
to upload
a file, parse it and save the objects:
-
admin.py
|
forms.py
|
my_app/templates/admin/my_app/
|
from
django.contrib import messages
from my_app.actions import
parse_mymodel_uploaded_file
from django.template.response
import TemplateResponse @admin.register(MyModel)
class
MyModelAdmin(admin.ModelAdmin):
model = MyModel
change_list_template =
'admin/my_app/change_list_import_template.html' |
|
change_list_import_template.html
{% extends
"admin/change_list.html" %}
{% load i18n %}
{% block object-tools-items %}
<li><a href="import_view/">{%
trans
"Import"
%}</a></li>
{{ block.super }}
{% endblock %}
|
def
get_model_info(self):
# module_name is renamed to
model_name in Django 1.8
app_label =
self.model._meta.app_label
try:
return
(app_label,
self.model._meta.model_name,)
except AttributeError:
return
(app_label,
self.model._meta.module_name,)
def get_urls (self):
urls = super(MyModelAdmin,
self).get_urls()
info = self.get_model_info()
my_urls = [
url(r'^import_view/$',
self.admin_site.admin_view(self.import_view),
name='%s_%s_import_view' % info)
]
return my_urls + urls |
|
|
def
import_view(self,
request, *args, **kwargs):
# custom view which should return
an HttpResponse
# if a GET (or any other method)
we'll create a blank form
if request.method != 'POST':
formulari
= ImportForm()
# if this is a POST request we
need to process the form data
else:
formulari
= ImportForm(request.POST,
request.FILES)
if formulari.is_valid():
#
do something:
errors
= parse_mymodel_uploaded_file(request.FILES['import_file'],
request.POST['input_format'])
if
errors is not None:
self.message_user(request,
"Errors:
%s" % errors,
level=messages.ERROR)
else:
self.message_user(request,
"Successful import")
url
= reverse('admin:%s_%s_changelist'
% self.get_model_info(),
current_app=self.admin_site.name)
return
HttpResponseRedirect(url)
context = {}
context['opts'] = self.model._meta
context['formulari'] = formulari
return TemplateResponse(request,
'admin/my_app/import_template.html',
context)
|
from
django import forms
from django.utils.translation
import ugettext_lazy as _
class ImportForm(forms.Form):
import_file =
forms.FileField(label=_('File to
import'))
input_format =
forms.ChoiceField(
label=_('Format'),
choices=(('auto','(auto)'),
('text/csv','csv'),
('application/vnd.ms-excel','xls'),
('text/xml','xml'),
('application/json','json'),
),
)
|
import_template.html
{% extends "admin/ base_myproject.html "
%}
{% load i18n %}
{% load admin_urls %}
{% block breadcrumbs_last %}
{% trans "Import" %}
{% endblock %}
{% debug %}
{% block content %}
<h1>{% trans "Import"
%}</h1>
{% trans "Import from file:"
%}
<form action="{{ form_url
}}" method="post"
enctype="multipart/form-data">
{%
csrf_token %}
{{ formulari
}}
<div
class="submit-row">
<input type="submit"
class="default" name="confirm"
value="{% trans "Import"
%}">
</div>
</form>
{{ block.super }}
{% endblock %}
|
- my_project/templates/admin/base_myproject.html
{% extends
"admin/base_site.html" %}
{% load i18n admin_static admin_modify
%}
{% load admin_urls %}
{% load url from future %}
{% block extrastyle %}{{ block.super
}}<link rel="stylesheet"
type="text/css" href="{% static
"admin/css/forms.css" %}" />{%
endblock %}
{% block bodyclass %}{{ opts.app_label
}}-{{ opts.object_name.lower }}
change-form{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index'
%}">{% trans 'Home' %}</a>
› <a href="{% url
'admin:app_list'
app_label=opts.app_label %}">{{
opts.app_label|capfirst|escape
}}</a>
› <a href="{% url
opts|admin_urlname:'changelist'
%}">{{
opts.verbose_name_plural|capfirst
}}</a>
› {% block breadcrumbs_last
%}{% endblock %}
</div>
{% endblock %}
{% endif %}
- actions.py
from
django.core.files import File
from rest_framework.compat import
BytesIO from
rest_framework.parsers import
JSONParser
def parse_mymodel_uploaded_file(uploaded_file,
content-type):
print
'content_type: %s' %
uploaded_file.content_type
errors = dict()
# get the
content-type from the uploaded file
if content_type ==
'auto':
content_type =
uploaded_file.content_type
if content_type ==
'application/json':
fitxer = File(uploaded_file)
content = fitxer.read()
stream = BytesIO(content)
data = JSONParser().parse(stream)
fitxer.close()
serializer =
SimpleEventSerializer(data=data,
many=True)
if serializer.is_valid():
serializer.save(force_insert=True)
else:
print "No parser for content type: %s"
%
(content_type)
errors['error'] = "No parser for
content type: %s" % (content_type)
return errors
- Afegir un botó a la llista (pàgina addicional
i pàgina de confirmació, sense
formularis)
admin.py
|
my_app/templates/admin/my_app/my_model/ |
from
django.contrib import admin, messages
from django.conf.urls import url
from
django.core.urlresolvers import
reverse
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
model = MyModel
# rendered at
django.contrib.admin.options.ModelAdmin.changelist_view
change_list_template = 'admin/my_app/my_model/change_list_apply_template.html' |
change_list.html
{% extends
"admin/change_list.html" %}
{% load i18n %}
{% block object-tools-items %}
<li><a href="apply_view/">{%
trans
"Apply" %}</a></li>
{{ block.super }}
{% endblock %}
|
def
get_model_info(self):
# module_name was renamed to
model_name in Django 1.8
app_label = self.model._meta.app_label
try:
return
(app_label,
self.model._meta.model_name,)
except AttributeError:
# Django < 1.8
return
(app_label,
self.model._meta.module_name,)
def get_urls (self):
urls = super(MyModelAdmin,
self).get_urls()
info =
self.get_model_info()
my_urls = [
url(r'^apply_view/$',
self.admin_site.admin_view(self.apply_view),
name='%s_%s_apply_view'
% info),
url(r'^confirmed_view/$',
self.admin_site.admin_view(self.confirmed_view),
name='%s_%s_confirmed_view'
%
info),
]
return my_urls + urls
|
|
def
apply_view(self,
request, *args, **kwargs):
# custom view which should return an
HttpResponse
active_objects =
MyModel.objects.filter(is_active=True)
context = {}
context['opts'] = self.model._meta
context['active_objects'] =
active_objects
return TemplateResponse(request,
'admin/my_app/apply_template.html',
context,
current_app=self.admin_site.name)
|
apply_template.html
{% extends "admin/base_myproject.html"
%}
{% load i18n %}
{% load admin_urls %}
{% load url from future %}
{% debug %}
{% block breadcrumbs_last %}
{% trans "Apply" %}
{% endblock %}
{% block content %}
<h1>{% trans "Apply"
%}</h1>
{% trans "The following objects
are active:" %}
{% if active_objects %}
<ul>
{% for active_object in active_objects
%}
<li>{{active_object.Name}}</li>
{% endfor %}
</ul>
{% else %}
{% endif %}
<form action="{% url opts|admin_urlname:"confirmed_view"
%}" method="POST">
{% csrf_token %}
<div
class="submit-row">
<input
type="submit" class="default"
name="confirm" value="{% trans
"Confirm" %}">
</div>
</form>
{{ block.super }}
{% endblock %}
|
def
confirmed_view(self,
request,
*args, **kwargs):
# do something:
do_something_with_some_objects()
success_message = _('Successfully
applied')
messages.success(request,
success_message)
url = reverse('admin:%s_%s_changelist'
% self.get_model_info(),
current_app=self.admin_site.name)
return HttpResponseRedirect(url)
|
|
- En el detall d'un element
- Afegir un botó que porti a una altra part de
l'admin (llista dels altres objected que estan
relacionats amb el meu via FK)
- ...
admin.py |
my_app/templates/admin/my_app/my_model/ |
from django.contrib import
admin, messages
from django.conf.urls import url
from django.core.urlresolvers
import reverse
@admin.register(MyModel)
class
MyModelAdmin(admin.ModelAdmin):
model = MyModel
#
rendered at
django.contrib.admin.options.ModelAdmin.render_change_form
change_form_template =
'admin/my_app/my_model/change_form.html'
|
change_form.html
{% extends "admin/change_form.html"
%}
{% load i18n %}
{% block object-tools-items %}
<li><a href={%
url 'admin:my_app_my_other_model_changelist'
%}?relation_to_mymodel__id__exact={{
object_id }}>{% trans "My
other model"
%}</a></li>
{{ block.super }}
{% endblock %}
|
- Afegir un botó que obri una pàgina intermèdia,
recuperi informació i faci alguna acció
- Django
Admin Actions on single object
-
admin.py |
my_app/templates/admin/my_app/my_model/ |
class
MyModel(admin.ModelAdmin):
model = MyModel
# rendered at
django.contrib.admin.options.ModelAdmin.render_change_form
change_form_template =
"admin/my_app/my_model/change_form.html" |
change_form.html
{% extends "admin/ change_form.html "
%}
{% load i18n %}
{% load admin_urls %}
{% block object-tools-items %}
<li><a
href="{% url
opts|admin_urlname:"my_action_intermediate_view"
object_id %}">{% trans
"Perform my action"
%}</a></li>
{{ block.super }}
{% endblock %}
|
def
get_model_info(self):
app_label =
self.model._meta.app_label
try:
return (
app_label,
self.model._meta.model_name,
)
except AttributeError:
return (
app_label,
self.model._meta.module_name,
)
def
get_urls(self):
urls = super(MediaItemAdmin,
self).get_urls()
info = self.get_model_info()
my_urls = [
url(
r"^(?P<pk>\d+)/my_action_intermediate_view/$",
self.admin_site.admin_view(self.my_action_intermediate_view),
name="%s_%s_my_action_intermediate_view"
% info,
),
url(
r"^(?P<pk>\d+)/my_action_view/$",
self.admin_site.admin_view(self.my_action_view),
name="%s_%s_my_action_view" %
info,
),
]
return my_urls + urls |
|
def my_action_intermediate_view(self,
request, *args, **kwargs):
active_object =
self.get_object(request,
kwargs["pk"])
context = {}
context["opts"] = self.model._meta
context["object_id"] =
kwargs["pk"]
context["active_object"] =
active_object
return TemplateResponse(
request,
"admin/my_app/my_model/my_action_template.html",
context,
) |
my_action_template.html
{% extends "admin/base_site.html"
%}
{% load i18n l10n admin_urls
static %}
{% block extrahead %}
{{ block.super }}
{{ media }}
<script
type="text/javascript" src="{%
static 'admin/js/cancel.js'
%}"></script>
{% endblock %}
{% block content %}
<p>
{% blocktrans %}Do
something for: {{
active_object }}
{% endblocktrans %}
</p>
<form action="{% url
opts|admin_urlname:"my_action_view"
object_id %}"
method="post">{% csrf_token
%}
<div>
<label
for="my_param">My
param:</label><br>
<input
type="text" id="my_param"
name="my_param"><br>
<input
type="submit" name="apply"
value="{% trans "Go" %}"/>
</div>
</form>
{% endblock %}
|
def my_action_view(self,
request, *args, **kwargs):
# custom view which should return
an HttpResponse
instance =
self.get_object(request,
kwargs["pk"])
# do something:
my_param = request.POST.get("my_param",
"")
if my_param:
instance.do_something(my_param)
success_message = _("Successfully
applied")
messages.success(request,
success_message)
else:
error_message = _("Empty
my_param")
messages.error(request,
error_message)
url = reverse(
"admin:%s_%s_changelist" %
self.get_model_info(),
current_app=self.admin_site.name,
)
return HttpResponseRedirect(url) |
|
- ...
- Afegir una acció per a
exportar / Add an action
to export
- actions.py
import os
from datetime import datetime
from django.conf import settings
from django.core.files import File
from rest_framework.renderers
import JSONRenderer
def export_mymodel_json(queryset)
serializer =
MyModelSerializer(queryset, many=True)
content =
JSONRenderer().render(serializer.data,
renderer_context={'indent':4})
now =
datetime.now()
filename =
os.path.join(settings.BASE_DIR,
"mymodel_%s.json" % (
now.strftime('%Y%m%d-%H%M%S') ))
f = open(filename,mode='w')
fitxer = File (f)
fitxer.write(content)
fitxer.close
return filename
- admin.py
from django.conf.urls
import patterns
from my_app.actions import export_mymodel_json,
parse_mymodel_uploaded_file
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
model = MyModel
# add action to export to a
local file
actions
= ['export_json_from_admin']
def
export_mymodel_json_from_admin(self, request,
queryset):
filename = export_mymodel_json(queryset)
self.message_user(request, "Successfully
written file: %s" % filename)
export_mymodel_json_from_admin.short_description
= _("Export as JSON to a local file")
- Django Admin - change header
'Django administration' text
- Admin
actions
- Example dealing with exceptions:
- models.py
class
MyModel(models.Model):
def do_something(self):
if
something_went_wrong:
raise
Exception("reason is ...")
- admin.py
from django.contrib import
admin
from django.utils.translation import ugettext_lazy
as _
from django.contrib import messages
from my_app.models import MyModel
@admin.register(Instance)
class MyModelAdmin(NestedModelAdmin):
model = MyModel
actions =
['first_action_from_admin']
def
first_action_from_admin(self, request, queryset):
for
instance in queryset:
try:
instance.do_something()
self.message_user(request,
'Successfully done something with object: %s' %
instance.name)
except
Exception as e:
self.message_user(request,
'Action could not be perfomed with instance %s
(%s)' % (instance.__unicode__, e),
level=messages.ERROR)
first_action_from_admin.short_description = _("Do
something with selected
instances")
- Example: write the result
of a serialization to a file
- actions.py
import os
from datetime import datetime
from rest_framework.renderers
import JSONRenderer
from django.core.files import File
from my_app.serializers import MyModelSerializer
def generate_json_file(queryset):
serializer =
MyModelSerializer(queryset, many=True)
content =
JSONRenderer().render(serializer.data,
renderer_context={'indent':4})
now = datetime.now()
filename =
os.path.join(settings.BASE_DIR, "nits_%s.json" % (
now.strftime('%Y%m%d-%H%M%S') ))
f =
open('contingut.json',mode='w')
fitxer = File(f)
fitxer.write(content)
fitxer.close
return filename
- admin.py
from my_app.actions import
generate_json_file
from django.utils.translation import ugettext_lazy
as _
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
model = MyModel
...
actions =
['generate_json_file_from_admin']
def generate_json_file_from_admin(self, request,
queryset):
filename = generate_json_file(queryset)
self.message_user(request, 'Successfully written
file: %s' % (filename))
generate_json_file_from_admin.short_description =
_("Generate JSON file")
- Example with a common function:
- models.py
class
MyModel1(models.Model):
def do_something(self):
if
something_went_wrong:
raise
Exception("reason is ...") class
MyModel2(models.Model):
def do_something(self):
if
something_went_wrong:
raise
Exception("reason is ...")
- admin.py
def common_action_from_admin( modeladmin,
request,
queryset ):
for instance in queryset:
try:
instance.do_something()
modeladmin.message_user(request,
'Successfully performed common action for: %s' %
instance.__unicode__())
except
Exception as e:
modeladmin.message_user(request,
'Could not perform action for %s (%s)' %
(instance.__unicode__(), e), level=messages.ERROR)
common_action_from_admin.short_description
=
_("Perform action")
@admin.register(MyModel1)
class MyModelAdmin(admin.ModelAdmin):
model = MyModel1
...
actions = [common_action_from_admin]
@admin.register(MyModel2)
class MyModelAdmin(admin.ModelAdmin):
model = MyModel2
...
actions = [common_action_from_admin]
- Missatges després de l'acció / Messages after the action
- Personalització d'accions / Action personalization
- Conditionally
enabling
or disabling actions
- Django
admin
snippets
- Pàgina de
confirmació / Confirmation page
- Custom
django admin actions with an intermediate page
- Django
Admin Action Confirmation Page
- The
default “delete selected” admin action in Django
- Exemple / Example
- my_app/admin.py
-
from django.shortcuts import render from django.http import HttpResponseRedirect
class MyModelAdmin(admin.ModelAdmin): actions = ['my_action']
def my_action(self, request, queryset): # perform the action if 'apply' in request.POST: # The user clicked submit on the intermediate form. # NOTE: a fallback "False" is needed because unchecked checkboxes will not be sent at all from html form
# then they are converted from string to boolean dry_run = eval(request.POST.get("dry_run", "False")) for instance in queryset: try: do_something_result = instance.do_something() self.message_user(request, 'Result of action performed on: {} -> {}'.format(instance, do_something_result)) except Exception as e: self.message_user(request, 'Could not perform action on: {} ({})'.format(instance, e), level=messages.ERROR) return HttpResponseRedirect(request.get_full_path()) # confirmation page context = {'queryset':queryset} return render(request, 'admin/my_app/my_action_confirmation_template.html', context) my_action.short_description = _("Perform action on selected items")
- my_app/templates/admin/my_app/my_action_confirmation_template.html
{% extends
"admin/base_site.html" %}
{% load i18n l10n admin_urls static %}
{% block extrahead %}
{{ block.super }}
{{ media }}
<script
type="text/javascript" src="{% static
'admin/js/cancel.js'
%}"></script>
{% endblock %}
{% block content %}
<p>
{% blocktrans %}Are you sure you want to
perform the action on these items?{%
endblocktrans %}
</p>
<form action="" method="post">{%
csrf_token %}
<div>
<ul>
{% for obj in queryset %}
<li>{{ obj
}}</li>
<!-- the following
line must be present in the loop, even if
you do not use the previous one, so as the
queryset is preserved -->
<input type="hidden"
name="_selected_action" value="{{ obj.pk
}}" />
{% endfor %}
</ul>
<!-- action value must match the
name given in MyModelAdmin, so as form
action="" redirects to the same action
method that called this -->
<input type="hidden"
name="action" value="my_action" />
<input type="submit"
name="apply" value="{% trans "Yes, I'm
sure" %}"/>
</div>
</form>
{% endblock %}
- Esborra / Delete
- The default “delete
selected” admin action in Django
- Canvi de nom / Change name
- ...py
from
django.contrib.admin.actions import
delete_selected
delete_selected.short_description =
u'Remove selected items'
- Deshabilita globalment i habilita localment /
Globally disable and localy enable
- urls.py
admin.site.disable_action('delete_selected')
- my_app/admin.py
class
FooAdmin(admin.ModelAdmin):
actions = ['my_action',
'my_other_action',
admin.actions.delete_selected]
- Deshabilita localment / Localy enable
- my_app/admin.py
class
MyModelAdmin(admin.ModelAdmin):
...
def get_actions(self,
request):
actions = super(MyModelAdmin,
self).get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
- admin.py
class
TotoAdmin(admin.ModelAdmin):
# filter available actions by
user
def get_actions(self,
request):
actions
= super(TotoAdmin, self).get_actions(request)
if not
request.user.is_superuser:
if '' in actions:
del
actions['first_admin_action']
if ' second_admin_action '
in actions:
del
actions['second_admin_action']
if
' third_admin_action ' in
actions:
del
actions['third_admin_action']
except
KeyError:
pass
return
actions
- Usuaris
/ Users
- Users
and
the admin
- models.py
class Toto(model.Model):
title =
models.CharField(max_length=250)
slug = models.SlugField()
usuari = models.ForeignKey(User,
related_name='totos')
...
- admin.py (Mostra
només
els objectes de l'usuari / Show only objects of user)
class
TotoAdmin(admin.ModelAdmin):
exclude = ('usuari',)
prepopulated_fields
= {"slug": ("title",)}
readonly_fields =
('non_editable_toto_field',)
# only owned objects
def has_change_permission(self,
request,
obj=None):
has_class_permission = super(TotoAdmin,
self).has_change_permission(request, obj)
if not
has_class_permission:
return
False
if obj is
not None and not request.user.is_superuser and
request.user.id != obj.usuari.id:
return
False
return True
# Django < 1.6
(for 1.6 use get_queryset instead)
def queryset(self,
request):
if
request.user.is_superuser:
return
Toto.objects.all()
return
Toto.objects.filter(usuari=request.user)
# Django 1.6 : queryset
has been renamed to get_queryset
def get_queryset(self,
request):
qs =
super(TotoAdmin, self).get_queryset(request)
if
request.user.is_superuser:
return
qs
return
qs.filter(usuari=request.user)
# automatically get the user from request
def save_model(self,
request, obj, form, change):
if not
change:
obj.usuari
= request.user
obj.save()
admin.site.register(Toto, TotoAdmin)
- Overwrite User unicode
- admin.py
def user_unicode(self):
string =
u'{}'.format(self.username)
return string
User.__unicode__ = user_unicode
admin.site.unregister(User)
admin.site.register(User)
- Accions recents / Recent
actions (log entries)
- View
all log entries in the admin 2
- admin.py
...
class LogEntryAdmin(admin.ModelAdmin):
date_hierarchy = 'action_time'
# Django <=1.8
#readonly_fields =
LogEntry._meta.get_all_field_names()
# Django >=1.11 (?)
readonly_fields = [f.name for f
in LogEntry._meta.get_fields()]
...
- ManyToManyField
- Working
with
many-to-many models (Inline)
- Access manytomany from the model that does define it:
- models.py
class
Person(models.Model):
name =
models.CharField(max_length=128)
def __unicode__(self):
return self.name
class Classroom(models.Model):
name =
models.CharField(max_length=128)
members =
models.ManyToManyField(Person,
related_name='classrooms')
def __unicode__(self):
return self.name
- admin.py
class
ClassroomAdmin(admin.ModelAdmin):
...
# by default, 'members' is
added as a regular field.
...
# if you want some different
presentation:
# filter_horizontal
= ('members',)
# filter_vertical
= ('members',)
admin.site.register(Classroom, ClassroomAdmin)
- Horizontal or vertical interface (two boxes)
- Access manytomany with through
(explicit or implicit):
- inline
of it
- implicit through
- models.py
class
Person(models.Model):
name =
models.CharField(max_length=128)
class Classroom(models.Model):
name =
models.CharField(max_length=128)
members =
models.ManyToManyField(Person,
related_name='classrooms')
- admin.py
class
ClassroomPersonInline(admin.TabularInline)
model =
Classroom.members.through
class ClassroomAdmin(admin.ModelAdmin):
...
inlines =
[ClassroomPersonInline,]
admin.site.register(Classroom, ClassroomAdmin)
- explicit through
- Access manytomany from the model that does not define it:
- option 1: use through
(every m2m relation has it, even if not explicitly
declared) and inline
of it
- models.py
class
Person(models.Model):
name =
models.CharField(max_length=128)
def
__unicode__(self):
return self.name
class Classroom(models.Model):
name =
models.CharField(max_length=128)
members =
models.ManyToManyField(Person,
related_name='classrooms')
def
__unicode__(self):
return self.name
- admin.py
class
ClassroomInline(admin.TabularInline):
model = Classroom.members.through
extra = 0
class PersonAdmin(admin.ModelAdmin):
...
inlines = [ ClassroomInline,]
admin.site.register(Person, PersonAdmin)
- option 2: horizontal_filter
- Django
admin interface: using horizontal_filter with
inline ManyToMany field
- admin.py
from django import
forms
from django.contrib.admin.widgets import
FilteredSelectMultiple
from django.contrib.auth.models import
User,Group
class CustomGroupAdminForm(forms.ModelForm):
class Meta:
model = Group
fields = ('name','permissions',)
users =
forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name='Users',
is_stacked=False
)
)
def __init__(self, *args,
**kwargs):
super(CustomGroupAdminForm,
self).__init__(*args, **kwargs)
if
self.instance.pk:
self.fields['users'].initial =
self.instance.user_set.all()
def save(self,
commit=True):
group = super(CustomGroupAdminForm,
self).save(commit=False)
if
commit:
group.save()
if
group.pk:
group.user_set = self.cleaned_data['users']
self.save_m2m()
return group
class CustomGroupAdmin(GroupAdmin):
form = CustomGroupAdminForm
admin.site.unregister(Group)
admin.site.register(Group, CustomGroupAdmin)
- Display choices as checkbox
- Filter choices
class
MyModelAdmin(admin.ModelAdmin):
model = MyModel
def formfield_for_manytomany(self,
db_field, request, **kwargs):
if
db_field.name == "cars":
kwargs["queryset"]
= Car.objects.filter(carType=CAR_TYPE_COUPE)
return
super(MyModelAdmin,
self).formfield_for_manytomany(db_field, request,
**kwargs)
class
MyModelAdmin(admin.ModelAdmin):
model = MyModel
def formfield_for_manytomany(self,
db_field, request, **kwargs):
if
db_field.name == "cars":
# get parent_id
parent_obj_id = request.resolver_match.args[0]
# only objects whose parent is the present one
kwargs["queryset"]
= Car.objects.filter(parent_field__id= parent_obj_id )
return
super(MyModelAdmin,
self).formfield_for_manytomany(db_field, request,
**kwargs)
- if many2many objects are added by code (e.g. in a
post_save receiver:
my_instance.my_related_objects.add(my_related_instance)
), the relations must be explicitly added:
- admin.py
class
MyModelAdmin(admin.ModelAdmin):
def save_related(self, request,
form, formsets, change):
super(MyModelAdmin, self).save_related(request,
form, formsets, change)
def save_model(self, request,
obj, form, change):
try:
super(MyModelAdmin,
self).save_model(request, obj, form, change)
# add your m2m here, so as they are saved in
save_related:
if obj.my_related_objects:
form.cleaned_data['my_related_objects']
= obj.my_related_objects.all()
except
Exception as e:
raise e
- list_display
- many-to-many
in
list display django
- manytomany column, with a different name:
- models.py
class
MyUser(AbstractUser):
...
def get_groups(self):
return ",".join([g.name for g in
self.groups.all()])
- admin.py
from my_app.models
import MyUser
class MyUserAdmin(UserAdmin):
model = MyUser
def grupets(self,item):
return item.get_groups()
list_display =
('username','grupets',)
admin.site.register(MyUser, MyUserAdmin)
- ForeignKey
- Can “list_display” in a Django
ModelAdmin display attributes of ForeignKey fields?
- Foreign keys in django admin
list display
- ForeignKey defined in the model
- ForeignKey defined in another model and pointing to
this model (also consider GenericForeignKey)
- admin.py
class TotoAdmin()
list_display = (...,
'name_of_foreign_key_field_url', ...)
...
def
name_of_foreign_key_field_url(self,item):
target
= getattr(item, 'name_of_foreign_key_field')
return
'<a href="../%s/%d/">%s</a>' %
(target._meta.module_name, target.id,
unicode(target))
name_of_foreign_key_field_url.allow_tags = True
name_of_foreign_key_field_url.short_description =
_("Name")
- Show related as a list, with links:
- myapp/models.py
class MyModel()
name =
models.CharField(max_length=50)
class SonModel()
mymodel =
models.ForeignKey(MyModel,
related_name='sons')
name =
models.CharField(max_length=50)
- myapp/admin.py
from django.contrib
import admin
@admin.register(MyModel)
class MyModeldmin(admin.ModelAdmin):
model = MyModel
list_display =
('name','sons_list',)
def sons_list(self, item):
sons = item.sons.all()
result = '<ul>'
for
son in sons:
url = reverse('admin:myapp_sonmodel_change',
args=[son.id])
result
= '%s <li><a
href="%s">%s</a></li>' %
(result, url, son.name)
result = "%s </ul>" % result
return result
sons_list .short_description
=
'Sons'
sons_list .allow_tags
= True
- Searchable (in a pop-up)
raw_id_fields = ('field_1',)
- Filter dropdown choices
formfield_for_foreignkey
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if
db_field.name ==
"car":
kwargs["queryset"]
= Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field,
request,
**kwargs)
class MyModelAdmin(admin.ModelAdmin):
def
formfield_for_foreignkey(self, db_field,
request, **kwargs):
if
db_field.name == 'car':
# if updating, get id for present object
if request.resolver_match.args:
object_id
= request.resolver_match.args[0]
if object_id:
...
return super(MixerAdmin,
self).formfield_for_foreignkey(db_field,
request, **kwargs)
- Prepopulate django filter
horizontal in admin
- Model
limit_choices_to={'user': user}
- Filtering
Dropdown
Lists in the Django Admin
- django admin inlines: get
object from formfield_for_foreignkey
parent_obj_id =
request.resolver_match.args[0]
- Filter when using raw_id_fields
- Display choices as radio-button
class
MyAdmin(admin.ModelAdmin):
radio_fields
= {"myforeignkeyfield": admin.HORIZONTAL}
- GenericForeignKey
- from model that has GenericForeignKey()
- admin.py
class
MyModelAdmin(admin.ModelAdmin):
fields =
('content_type','content_id',)
def
formfield_for_foreignkey(self, db_field, request,
**kwargs):
if
db_field.name == "content_type":
# only allowed content types: MyFirstModel,
MySecondModel
kwargs["queryset"] =
ContentType.objects.filter(model__in=['myfirstmodel','mysecondmodel',])
return
super(MyModelAdmin,
self).formfield_for_foreignkey(db_field, request,
**kwargs)
- Magnifying glass to be able to search for related
objects
- forms.py
- from
django.forms import ModelForm
from django.contrib.admin.widgets import
ForeignKeyRawIdWidget
from django.contrib.admin.sites import site
from django.db.models.fields.related import
ManyToOneRel
from .models import MyModel
class MyModelAdminForm(ModelForm):
def __init__(self, *args,
**kwargs):
super(MyModelAdminForm, self).__init__(*args,
**kwargs)
try:
model =
self.instance.content_type.model_class()
model_key = model._meta.pk.name
except:
# when creating, we do not have a content_type
yet
model = MyDefaultRelatedModel
model_key = "id"
rel
= ManyToOneRel(model._meta.get_field("id"),
model, model_key)
self.fields["object_id"].widget =
ForeignKeyRawIdWidget(rel, site)
class Meta(object):
model = MyModel
fields = "__all__"
- admin.py
from my_app.forms
import MyModelAdminForm
class MyModelAdmin(admin.ModelAdmin):
form = MyModelAdminForm
- from model that has not GenericForeignKey()
- Using
generic relations as an inline
- admin.py
from
django.contrib.contenttypes.admin import
GenericTabularInline
class TaggedItemInline(GenericTabularInline):
model = TaggedItem
# show a link to go to the
TaggedItem from BookmarkAdmin
show_change_link = True
class BookmarkAdmin(admin.ModelAdmin):
...
inlines =
[TaggedItemInline,]
- ...
- Admin
widget for generic relations in django (code that has
some errors)
- django-genericadmin
- django-genericrelationview
- django-admin-genericfk
(does not work with Django 1.9)
- Resizing forms
- Problemes / Problems
|
|
- Internationalization
and
localization
- Django
packages for internationalization
- Rosetta
- Easymode
- Serialization
- Unicode in Python
- coding in file.py
- in Python files:
from django.utils.translation
import ugettext_lazy
as _
...
verbose_name = _("Nom del model")
from django.utils import
translation
current_language = translation.get_language()
from django.utils import
translation
current_language = translation.get_language()
forced_language = 'ca'
translation.activate(forced_language)
... translation.activate(current_language)
- Utilització amb variables / Usage with variables
- file1.py
# do not use aliases;
string would not appear in django.po:
# from django.utils.translation import
ugettext_lazy as _, ugettext_noop as _noop
from django.utils.translation import ugettext_lazy
as _, ugettext_noop
my_var = ugettext_noop("My translatable string")
my_func(my_var)
- file2.py
from
django.utils.translation import ugettext_lazy as _
def my_func(cadena)
print _(cadena)
- in templates:
{% load i18n %}
{% trans "My text" %}
- Celery tasks
- ...py
from django.utils import
translation
...
ctx = {
...
'lang':translation.get_language(),
}
do_something.delay(...,ctx)
- tasks.py
from django.utils import
translation
@shared_task
def do_something_task(..., ctx):
lang = ctx.get('lang',
settings.LANGUAGE_CODE)
translation.activate(lang)
...
- Activation of i18n (user can select the language according to
the browser preferences: Accept-Language
/ HTTP_ACCEPT_LANGUAGE):
- project_name/settings.py (order is important)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
...
- Activation of i18n for
project_name:
- dependències / dependencies
- primera vegada / first time
cd project_name; mkdir locale
- nova llengua / new language
- si la llengua que voleu no està a la llista
LANG_INFO (django/conf/locale/__init__.py), caldrà
afegir-la als settings:
- project_name/settings.py
- Django > ...
- Django < ...
import os
PROJECT_PATH
=
os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
LOCALE_PATHS = (
PROJECT_PATH
+ '/locale/',
)
- afegiu la traducció al català (ca) / add Catalan (ca)
translation:
django-admin.py makemessages -l ca
- això generarà els fitxers .po:
- then translate locale/ca/LC_MESSAGES/django.po (e.g.
using lokalize)
- lokalize
- Project / New project
- my_project/locale/index.lokalize
- django.po
- workaround for non existing ignore option for
compilemessages
cd src/myproject/myproject
../manage.py
compilemessages
django-admin.py
compilemessages
- següents vegades / next times:
cd project_name
./manage.py
makemessages
-a --ignore="env*"
django-admin.py
makemessages -a
- translate locale/ca/LC_MESSAGES/django.po (e.g. using
lokalize)
- workaround for non existing ignore option for
compilemessages
cd src/myproject/myproject
../manage.py compilemessages
- Activation of i18n for project_name/app_name:
- Com per a un projecte, però no cal LOCALE_PATHS / Same as
above, but LOCALE_PATHS is not needed
- Third party modules
- Language in user profile
- i18n of models
- Related
projects (django-modeltranslation)
- django-modeltranslation
- Use
new
meta API with django 1.8 #299
- assumint que el camp amb traduccions es diu «name»
- el camp «name» ja no serà accessible
- la llengua per defecte és:
- un cop determinada la llengua (si no s'especifica,
es considera que xx és la llengua per defecte), el
camp «name» queda temporalment enllaçat cap a
«name_xx»
- la llengua es pot forçar:
- fora de les vistes:
translation.activate('ca')
# will be the same as name_ca:
myobject.name
- #
you can directly access the translated
version, without setting the language
myobject.name_ca
- a les vistes:
- s'agafarà el valor que li arriba amb la
capçalera Accept-Language (si no
s'especifica, s'agafarà la llengua per
defecte)
- als testos unitaris:
res = self.client.get(
url,
**{'HTTP_ACCEPT_LANGUAGE': lang}
)
- ...
- Management
commands
- quan la base de dades ja existeix i s'afegeix la
internacionalització d'un camp, cal poblar, almenys,
el camp_xx, on xx és la llengua per defecte /
for existing objects when modeltranslation is added:
python manage.py update_translation_fields
- to populate other languages than the default
one:
python manage.py
update_translation_fields --language=ca
- to populate only fields in models in a
specific app:
python manage.py
update_translation_fields myapp
- to populate only fields in specific model:
python manage.py
update_translation_fields myapp mymodel
- get active language:
import modeltranslation
modeltranslation.utils.get_language()
- set active language (e.g. outside a view):
from django.utils import
translation
translation.activate('ca')
- unset active language
- Utilització amb djangorestframework
/ Usage with djangorestframework
- NOT NEEDED: How
to
handle translations when serializing?
- curl
curl --user admin:admin
http://127.0.0.1:8000/api/toto/?format=json
--header 'Accept-Language: ca'
- override language (force language by query params,
even if accept language is sent)
from
django.utils import translation
class
OverrideTranslateSerializer(serializers.ModelSerializer):
def __init__(self, *args,
**kwargs):
super(OverrideTranslateSerializer,
self).__init__(*args, **kwargs)
if
kwargs.get('context', None):
request
= kwargs['context'].get('request', None)
forced_lang
= request.QUERY_PARAMS.get('lang',None)
if forced_lang:
translation.activate(forced_lang)
curl --user admin:admin
"http://127.0.0.1:8000/api/toto/1/?lang=fr"
--header 'Accept-Language: ca'
curl -X PATCH -H
"Content-Type:application/json" -d
'{"name":"nouveau nom"}' --user admin:admin
"http://127.0.0.1:8000/api/toto/1/?lang=fr"
--header 'Accept-Language: ca'
pip install django-modeltranslation
- my_project
- my_project
- settings.py
INSTALLED_APPS = (
# must be in the first
place
'modeltranslation',
'django.contrib.admin',
...
)
# modeltranslation
gettext = lambda s: s
LANGUAGES = (
('en',
gettext('English')),
('ca',
gettext('Catalan')),
('es',
gettext('Spanish')),
('fr',
gettext('French')),
)
# modeltranslation
from django.utils.translation import
ugettext_lazy as _
LANGUAGES = (
('en', _('English')),
('ca', _('Catalan')),
('es', _('Spanish')),
('fr', _('French')),
)
- my_app
- translation.py (parallel to models.py, where
translatable fields are defined)
from
modeltranslation.translator import
translator, TranslationOptions
from my_app.models import MyModel
class
MyModelTranslationOptions(TranslationOptions):
fields = ('name',
'description',)
translator.register(MyModel,
MyModelTranslationOptions)
- admin.py
from
django.contrib import admin
from modeltranslation.admin import
TranslationAdmin, TabbedTranslationAdmin
from my_app.models import MyModel
class
MyModelAdmin(TabbedTranslationAdmin):
#class MyModelAdmin(TranslationAdmin):
group_fieldsets = False
admin.site.register(MyModel, MyModelAdmin)
- my_project amb herència /
with inheritance
- my_project
- settings.py
INSTALLED_APPS = (
# must be in the first
place
'modeltranslation',
'django.contrib.admin',
...
)
# modeltranslation
from django.utils.translation import
ugettext_lazy as _
LANGUAGES = (
('en', _('English')),
('ca', _('Catalan')),
('es', _('Spanish')),
('fr', _('French')),
)
- my_app
- models.py
from django.db
import models
from model_utils.managers import InheritanceManager
class Place(models.Model):
name =
models.CharField(max_length=50)
address =
models.CharField(max_length=80)
objects =
InheritanceManager()
class Restaurant(Place):
serves_hot_dogs =
models.BooleanField(default=False)
serves_pizza =
models.BooleanField(default=False)
class Bar(Place):
serves_alcohol =
models.BooleanField(default=False)
- translation.py
from
modeltranslation.translator import
translator, TranslationOptions
from my_app.models import Place,
Restaurant, Bar
class
PlaceTranslationOptions(TranslationOptions):
fields = ('name',)
translator.register(Place,
PlaceTranslationOptions)
class
RestaurantTranslationOptions(TranslationOptions):
pass
translator.register(Restaurant,
RestaurantTranslationOptions)
class
BarTranslationOptions(TranslationOptions):
pass
translator.register(Bar,
BarTranslationOptions)
- admin.py
from
django.contrib import admin
from modeltranslation.admin import
TranslationAdmin, TabbedTranslationAdmin
from models import Place, Restaurant, Bar
@admin.register(Place)
class PlaceAdmin(TabbedTranslationAdmin):
list_display = ('name',
'type', 'address', )
@admin.register(Restaurant)
class
RestaurantAdmin(TabbedTranslationAdmin):
pass
@admin.register(Bar)
class BarAdmin(TabbedTranslationAdmin):
pass
- Problemes / Problems
modeltranslation.translator.NotRegistered
- Internationalization
and
localization of django models, with admin support
(django-easymodel)
|
Tests
|
- Python unit
tests
- Testing
in
Django
- Tutorial
part
5
- Integration
with
coverage.py
- Overriding
settings
from django.test import override_settings
@override_settings(...)
def ...
- Run tests
./manage.py test
- parallel
pip install tblib
./manage.py test --parallel
./manage.py test myapp
./manage.py test myapp....
./manage.py test --settings ... --verbose 3
- From Eclipse
- Email
- Testing tools: Email
services
- mytest.py
from django.core import
mail
class MyFirstTests(TestCase):
def my_first_test(self):
from
django.core import mail
...
#
verify the number of sent messages
self.assertEqual(len(mail.outbox), 1)
#
verify that the subject of the first message is
correct
self.assertEqual(mail.outbox[0].subject, ...)
- PostgreSQL
- cannot
create extension without superuser role
- Mageia
- CentOS
psql
# 1. temporarily set user as
superuser:
alter role user_name superuser;
# on aws aurora
# grant rds_superuser to user_name;
# 2. run tests, that will create postgis extension
# 3. set user as no superuser:
alter role user_name nosuperuser;
- Problemes / Problems
psycopg2.InterfaceError: connection already
closed
- Excepcions / Exceptions
- Integrity
error and atomic transactions
from django.db.utils import
IntegrityError
from django.db import transaction
def
test_call_to_method_that_should_raise_an_exception(self):
with transaction.atomic():
with
self.assertRaisesRegex(IntegrityError, 'duplicate key
value violates unique constraint'):
# put your call to method that should raise an
IntegrityError exception
- Djangorestframework
- Exemples / Examples
from rest_framework.test
import APITestCase
from rest_framework import status
class MyTestAPITestCase(APITestCase):
# define user for subsequent authenticated
requests
self.client.force_authenticate(my_user)
# get
res = self.client.get(url,
query_params_dict)
# get with request
headers
headers = {...}
res =
self.client.get(url,
post_params_dict, headers=headers)
# get with preferred language
res =
self.client.get(url,
post_params_dict,
**{"HTTP_ACCEPT_LANGUAGE":
"ca"})
# cors
headers in preflight (django-cors-headers)
res
= self.client.options(url,
**{"HTTP_ACCESS_CONTROL_REQUEST_METHOD": "GET"},
**{"HTTP_ORIGIN": "http://localhost/"},
)
# post (multipart)
res = self.client.post(url,
post_params_dict)
# post (json)
res = self.client.post(url,
post_params_dict, format="json")
# print result
print("{} {}".format(res.status_code, res.data))
print("response headers:
{}".format(res._headers))
# verify result
self.assertEqual(res.status_code,
status.HTTP_200_OK)
self.assertEqual(res.data[])
- None in payload
- when trying to set a related field (declared using
allow_null=True )
to None
- Note that (implicit) PrimaryKeyRelatedField
has allow_null=True
- see RelatedField.run_validation()
- Django
REST Framework's APIClient sends None as 'None'
- Solució / Solution
- using JSON:
res = self.client.post(..., {"myfield":None},
format="json")
- using multipart:
res = self.client.post(...,
{"myfield":""})
res = self.client.post(...,
{"myfield":""},
format="multipart")
- list in a comma-separated queryparam
-
url |
tests.py |
views.py |
myparam=first,second,third |
res = self.client.get(url,
{"myparam": ",".join(myparam_list)}) |
my_param =
openapi.Parameter(
"myparam",
openapi.IN_QUERY,
description="...",
type=openapi.TYPE_ARRAY,
items=openapi.Items(type=openapi.TYPE_STRING),
)
@swagger_auto_schema(manual_parameters=[my_param])
def my_view(...)
myparam_list =
request.query_params["myparam"].split(",")
|
myparam_lists =
request.query_params.getlist("myparam")
# put
myparam=first,second&myparam=third
into a single list
myparam_list = []
for myparam_list_element in myparam_lists:
myparam_list +=
myparam_list_element.split(",") |
myparam=first&myparam=second&myparam=third |
res = self.client.get(url,
{"myparam": myparam_list}) |
myparam_list =
request.query_params.getlist("myparam")
|
- views.py
myparam_list =
request.query_params.getlist("myparam")
# request.query_params["myparam"]
returns only the last value: "second"
- test.py
myparam_list =
["first", "second"]
# <url>?myparam=first,second
res = self.client.get(url, {"myparam":
myparam_list})
- empty file
- when trying to upload an empty file to a field
declared with allow_empty_file=True:
my_attached_file =
serializers.FileField(write_only=True,
required=False, allow_empty_file=True)
- Solució / Solution:
- FileField, ImageField, ...
- database
from django.test import
TestCase
from django.core.files.uploadedfile import
SimpleUploadedFile
class MyTests(TestCase):
def test_mymodel_create(self):
mymodel
= MyModel.objects.create(
name = 'My first instance',
image = SimpleUploadedFile(name='test_image.png',
content=open(image_path, 'rb').read(),
content_type='image/png'),
)
- djangorestframework
from rest_framework.test
import APITestCase
class MyAPITests(APITestCase):
def
test_mymodel_api_create(self):
with
open('test_image.png') as fp:
res = self.client.post(url, {'name': 'My first
instance', 'image': fp})
- Dades inicials en tests
unitaris / Initial dataset in unit tests
- Exemples / Examples
- disable logging
import logging
class MyModelAPITestCase(APITestCase):
def setUp(self):
logging.disable(logging.DEBUG)
- rest_auth
- tests.py
from django.test
import TestCase
from django.urls import reverse
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase
from allauth.account.models import
EmailAddress
from my_app.models import MyModel
class MyModelAPITestCase(APITestCase):
# run with: ./manage.py
test my_app.tests.MyModelAPITestCase
email = 'user@example.org'
password = '...'
key = ''
def login(self):
url
= reverse('rest_login')
data = {'email':self.email,
'password':self.password}
response = self.client.post(url, data,
format="json")
self.key = response.data['key']
print u"key: {}".format(self.key)
def register(self):
url
= reverse('rest_register')
data = {'email':self.email,
'password1':self.password,
'password2':self.password}
response = self.client.post(url, data,
format="json")
print u"response: {}".format(response)
#
mark email as verified
email = EmailAddress.objects.get()
email.verified = True
email.save()
def setUp(self):
#
create a user
#user =
User.objects.create_user(self.email,self.email,self.password)
#
register a user
self.register()
def
test_create_mymodel(self):
"""Create a mymodel (API REST)"""
#
login as user
self.login()
self.client.credentials(HTTP_AUTHORIZATION='Token
' + self.key)
url
= reverse('mymodel-list')
data = {'name': 'test'}
response = self.client.post(url, data,
format="json")
#
optional headers:
# response
= self.client.post(url, data, format="json", **{'HTTP_ACCEPT_LANGUAGE':'ca'})
self.assertEqual(response.status_code,
status.HTTP_201_CREATED)
self.assertEqual(MyModel.objects.count(), 1)
self.assertEqual(MyModel.objects.get().name,
'test')
./manage test [--keepdb] ...
- Testing
(Djangorestframework)
- CRUD
-
|
url (Swagger) |
urls.py |
myapp/views.py |
(reverse)
|
myapp/tests/test_mymodel.py |
./manage test myapp.tests.test_mymodel. |
database |
|
|
|
|
from django.test import TestCase
class MymodelTests(TestCase):
""" Test Mymodel in database """
def setUp(self):
... |
MymodelTests |
|
|
|
|
def test_mymodel_create(): |
MymodelTests.test_mymodel_create |
|
|
|
|
def test_mymodel_list(): |
MymodelTests.test_mymodel_list |
|
|
|
|
def test_mymodel_retrieve(): |
MymodelTests.test_mymodel_retrieve |
|
|
|
|
def test_mymodel_update(): |
MymodelTests.test_mymodel_update |
|
|
|
|
def test_mymodel_delete(): |
MymodelTests.test_mymodel_delete |
|
|
# myproject/urls.py
urlpatterns = [
url(r'^mynamespace/',
include('myapp.urls', namespace='mynamespace_myapp')),
] |
|
|
|
|
not nested views |
|
|
|
|
from django.contrib.auth.models import User
from rest_framework.test import APITestCase
class MymodelAPITests(APITestCase):
""" Test Mymodel through API """
def setUp(self):
self.user =
User.objects.create_user()
self.client.force_authenticate(self.user) |
MymodelAPITests |
/mynamespace/mymodelprefix/ |
# myapp/urls.py
from rest_framework import routers
mymodel_router = routers.SimpleRouter()
mymodel_router.register(r'mymodelprefix',
views.MymodelViewset)
urlpatterns = [
url(r'^',
include(mymodel_router.urls)),
] |
class
MymodelViewSet(viewsets.ModelViewSet):
queryset = Mymodel.objects.all()
serializer_class = MymodelSerializer
lookup_field = 'fieldc' |
MYMODEL_LIST_API_URL = lambda:
reverse(
'mynamespace:mymodel-list'
) |
def test_mymodel_api_create(self):
res =
self.client.post(
MYMODEL_LIST_API_URL(), {<mymodel_fields>}
) |
MymodelAPITests.test_mymodel_api_create |
def test_mymodel_api_list(self):
res =
self.client.get(
MYMODEL_LIST_API_URL(), {<queryparams>}
) |
MymodelAPITests.test_mymodel_api_list |
/mynamespace/mymodelprefix/{fieldc}/ |
MYMODEL_DETAIL_API_URL =
lambda mymodel_field1: reverse(
'mynamespace:mymodel-detail',
args=(mymodel_field1,)
) |
def
test_mymodel_api_retrieve(self):
#
mymodel_field1: value of field used to lookup (e.g.
uuid)
res =
self.client.get(
MYMODEL_DETAIL_API_URL(mymodel_field1),
{<queryparams>}
) |
MymodelAPITests.test_mymodel_api_retrieve |
def test_mymodel_api_update(self):
#
mymodel_field1: value of field used to lookup (e.g.
uuid)
res =
self.client.patch(
MYMODEL_DETAIL_API_URL(mymodel_field1),
{<mymodel_fields>}
) |
MymodelAPITests.test_mymodel_api_update |
def test_mymodel_api_delete(self):
#
mymodel_field1: value of field used to lookup (e.g.
uuid)
res =
self.client.delete(
MYMODEL_DETAIL_API_URL(mymodel_field1)
) |
MymodelAPITests.test_mymodel_api_delete |
/mynamespace/mymodelprefix/{fieldc}/custom_view/ |
class MymodelViewSet(viewsets.ModelViewSet):
@action(detail=True,
methods=['get'])
def custom_view(self, request, *args,
**kwargs): |
MYMODEL_CUSTOMVIEW_API_URL = lambda mymodel_field1:
reverse(
'mynamespace:mymodel-custom-view',
args=(mymodel_field1,)
) |
def
test_mymodel_api_customview(self):
#
mymodel_field1: value of field used to lookup (e.g.
uuid)
res =
self.client.get(
MYMODEL_CUSTOMVIEW_API_URL(mymodel_field1),
{<queryparams>}
) |
|
nested views |
|
|
|
|
from django.contrib.auth.models import User
from rest_framework.test import APITestCase
class ParentmodelMymodelAPITests(APITestCase):
""" Test Mymodel through nested API
"""
def setUp(self):
self.user =
User.objects.create_user()
self.client.force_authenticate(self.user) |
ParentmodelMymodelAPITests |
/mynamespace/parentmodelprefix/{pmodel_fieldp}/mymodelprefix/ |
# myapp/urls.py
from rest_framework_nested import routers
parentmodel_router = routers.SimpleRouter()
parentmodel_router.register(r'parentmodelprefix',
views.ParentmodelViewSet)
parentmodel_mymodel_router =
routers.NestedSimpleRouter(parentmodel_router, r'parentmodelprefix',
lookup='pmodel')
parentmodel_mymodel_router.register(r'mymodelprefix',
views.MymodelParentmodelViewSet )
#parentmodel_mymodel_router.register(r'mymodelprefix',
views.MymodelParentmodelViewSet, basename='mybasename')
urlpatterns = [
url(r'^',
include(mymodel_router.urls)),
] |
class
ParentmodelViewSet(viewsets.ModelViewSet):
queryset = Parentmodel.objects.all()
serializer_class =
ParentmodelSerializer
lookup_field = 'fieldp'
class MymodelParentmodelViewSet(viewsets.ModelViewSet):
queryset = Mymodel.objects.all()
serializer_class = MymodelSerializer
lookup_field = 'fieldc'
# used by list, retrieve, update,
partial_update, destroy
def get_queryset(self):
# only
entries that belong to specified parent
parentmodel_fieldp = self.kwargs.get('pmodel_fieldp',
None)
parent_object
= get_object_or_404(Parentmodel, fieldp=parentmodel_fieldp)
queryset =
Mymodel.objects.filter(field_to_parent=parent_object)
return
queryset
# used by create
def perform_create(self, serializer):
# deal with
exceptions
try:
# try something
except
Exception as e:
# raise Http404
raise APIException("Error: %s" % e)
# get the
parent from the lookup field
parentmodel_fieldp = self.kwargs.get('pmodel_fieldp',
None)
parent_object
= get_object_or_404(Parentmodel, fieldp=parentmodel_fieldp)
serializer.save(field_to_parent=parent_object) |
PARENTMODEL_MYMODEL_LIST_API_URL
= lambda parentmodel_fieldp: reverse(
'mynamespace:mymodel-list',
args=(parentmodel_fieldp,)
)
#PARENTMODEL_MYMODEL_LIST_API_URL = lambda
parentmodel_fieldp: reverse(
'mynamespace:mybasename-list',
args=(parentmodel_fieldp,)
) |
def
test_mymodel_nested_api_create(self):
#
parentmodel_field1: value of field used to lookup
Parentmodel (e.g. uuid)
res =
self.client.post(
PARENTMODEL_MYMODEL_LIST_API_URL(parentmodel_fieldp),
{<mymodel_fields>}
) |
ParentmodelMymodelAPITests.test_mymodel_nested_api_create |
def
test_mymodel_nested_api_list(self):
#
parentmodel_field1: value of field used to lookup
Parentmodel (e.g. uuid)
res =
self.client.get(
PARENTMODEL_MYMODEL_LIST_API_URL(parentmodel_fieldp),
{<queryparams>}
) |
ParentmodelMymodelAPITests.test_mymodel_nested_api_list |
/mynamespace/parentmodelprefix/{pmodel_fieldp}/mymodelprefix/{fieldc}/ |
|
PARENTMODEL_MYMODEL_DETAIL_API_URL
= lambda parentmodel_fieldp, mymodel_fieldc: reverse(
'mynamespace:mymodel-detail',
args=(parentmodel_fieldp, mymodel_fieldc,)
)
#PARENTMODEL_MYMODEL_DETAIL_API_URL = lambda
parentmodel_fieldp, mymodel_fieldc: reverse(
'mynamespace:mybasename-detail',
args=(parentmodel_fieldp, mymodel_fieldc,)
) |
def
test_mymodel_nested_api_retrieve(self):
#
parentmodel_field1: value of field used to lookup
Parentmodel (e.g. uuid)
#
mymodel_field1: value of field used to lookup (e.g.
uuid)
res =
self.client.get(
PARENTMODEL_MYMODEL_DETAIL_API_URL(parentmodel_fieldp,
mymodel_fieldc), {<queryparams>}
) |
ParentmodelMymodelAPITests.test_mymodel_nested_api_retrieve |
def
test_mymodel_nested_api_update(self):
#
parentmodel_field1: value of field used to lookup
Parentmodel (e.g. uuid)
#
mymodel_field1: value of field used to lookup (e.g.
uuid)
res =
self.client.patch(
PARENTMODEL_MYMODEL_DETAIL_API_URL(parentmodel_fieldp,
mymodel_fieldc), {<mymodel_fields>}
) |
ParentmodelMymodelAPITests.test_mymodel_nested_api_update |
def
test_mymodel_nested_api_delete(self):
#
parentmodel_field1: value of field used to lookup
Parentmodel (e.g. uuid)
#
mymodel_field1: value of field used to lookup (e.g.
uuid)
res =
self.client.delete(
PARENTMODEL_MYMODEL_DETAIL_API_URL(parentmodel_fieldp,
mymodel_fieldc)
) |
ParentmodelMymodelAPITests.test_mymodel_nested_api_delete |
/mynamespace/parentmodelprefix/{pmodel_fieldp}/mymodelprefix/{fieldc}/custom_view/ |
|
@action(detail=True,
methods=['get'])
def custom_view(self, request, *args,
**kwargs): |
PARENTMODEL_MYMODEL_CUSTOMVIEW_API_URL = lambda
parentmodel_fieldp, mymodel_fieldc: reverse(
'mynamespace:mymodel-custom-view',
args=(parentmodel_fieldp, mymodel_fieldc,)
) |
def
test_mymodel_api_customview(self):
#
parentmodel_field1: value of field used to lookup
Parentmodel (e.g. uuid)
#
mymodel_field1: value of field used to lookup Mymodel
(e.g. uuid)
res =
self.client.get(
PARENTMODEL_MYMODEL_CUSTOMVIEW_API_URL(parentmodel_fieldp,
mymodel_fieldc), {<queryparams>}
) |
ParentmodelMymodelAPITests.test_mymodel_nested_api_customview |
- Mock
-
|
function definition |
function call
(we are mocking this function call;
not the function definition) |
function test (same value is mocked every time) |
function test (different mocked values every time,
or exceptions) |
|
def my_function(...):
return my_value |
def my_caller_function(...):
returned_value = my_function() |
from mock import patch
@patch('path.to.my_function')
def test_my_caller_function(..., mock_my_function):
mock_my_function.return_value
= my_fake_value |
from mock import patch
@patch('path.to.my_function')
def test_my_caller_function(..., mock_my_function):
mock_my_function.side_effect
= [my_fake_value_a, my_fake_value_b] |
|
def my_function(...):
my_object.my_attr_1 = my_value_1
my_object.my_attr_2 = my_value_2
return my_object |
def my_caller_function(..):
returned_object = my_function() |
from mock import patch, Mock
@patch('path.to.my_function')
def test_my_caller_function(..., mock_my_function):
mock_my_function.return_value
= Mock(my_attr_1=my_fake_value1,
my_attr_2=my_fake_value2) |
from mock import patch
@patch('path.to.my_function')
def test_my_caller_function(..., mock_my_function):
mock_my_function.side_effect
= Exception("not working")
|
|
(my_app/models.py)
class MyModel():
def my_method(...) |
|
#@patch("my_app.MyModel.my_method")
@patch("my_app.models.MyModel.my_method")
def test_my_caller_function(...,
mock_my_model_my_method):
... |
|
requests |
(standard requests module) |
(my_dir/my_file.py)
import requests
class MyClass(object):
def my_caller_function(self,
...):
res =
requests.post(...) |
from mock import patch, Mock
# NO:
@patch('my_dir.my_file.MyClass.my_caller_function.requests')
@patch('my_dir.my_file.requests')
def test_my_caller_function(self, mock_requests):
# when response.text is used:
# mock_requests.post.return_value
= Mock(status_code=200, ok=True,
text=json.dumps({'key':'1234'}))
# when response.json() is called:
mock_requests.post.return_value
= Mock(status_code=200, ok=True,
json=Mock(return_value={'key':'1234'})) |
from mock import patch, Mock
@patch('path.to.my_caller_function.requests')
def test_my_caller_function(self, mock_requests):
# define
some results for the first four times the post is
called:
mock_requests.post.side_effect = [
Mock(status_code=200, ok=True,
json=Mock(return_value={'key':'1234'})),
Mock(status_code=201, ok=True),
Mock(status_code=200, ok=True,
json=Mock(return_value={'key':'1234'})),
Mock(status_code=201, ok=True,
json=Mock(return_value={'my_field':False}))
]
|
subprocess |
|
(my_dir/my_file.py)
res = subprocess.run() |
from mock import patch, Mock
@patch("my_dir.my_file.subprocess")
def test_my_caller_function(self, mock_subprocess):
mock_subprocess.run.return_value
= Mock(...) |
|
open text file |
|
(my_dir/my_file.py)
content = open("toto.txt", "r").read().strip() |
from mock import patch, mock_open
@patch("my_dir.my_file.open")
def test_my_caller_function(self, mock_open):
mock_open = mock_open()
mock_open.read.return_value = "my
content" |
|
open json file |
|
(my_dir/my_file.py)
with open("toto.json", "r") as f:
my_dict_or_list = json.load(f) |
from mock import patch, Mock, mock_open
@patch("my_dir.my_file.json.load")
@patch("my_dir.my_file.open")
def test_my_caller_function(self, mock_open,
mock_json_load):
mock_open = mock_open()
mock_json_load.return_value =
{"a": "b"} |
|
urlopen |
|
(my_dir/my_file.py)
with urlopen(url) as my_file: |
from mock import patch, MagicMock
@patch("my_dir_my_file.urlopen")
def test_my_caller_function(self, mock_urlopen):
# mock urlopen
# Python
3 urlopen context manager mocking
cm = MagicMock()
cm.getcode.return_value = 200
cm.getheaders.return_value = [
("Content-Type", "application/x-subrip"),
]
cm.read.return_value = "contents"
cm.__enter__.return_value = cm
mock_urlopen.return_value = cm |
|
Celery task |
from celery import
shared_task
@shared_task(...)
def my_function(...): |
def my_caller_function(...):
job_result =
my_function.apply_async( (...), ) |
import uuid
from mock import patch, Mock
@patch('path.where.is.called.my_function.apply_async')
def test_my_caller_function(...,
mock_my_function_apply_async):
# my_function is not really called
job_uuid = uuid.uuid4()
mock_my_function_apply_async.returned_value =
Mock(id=job_uuid)
my_caller_function(...) |
|
|
|
function_result = my_function(...) |
from mock import patch
@patch('path.to.my_task.run')
def test_my_caller_function(...,
mock_my_function_run):
#
my_function_is_not_really_called
mock_my_function_run.returned_value = ...
my_caller_function(...) |
|
Boto3 |
|
|
from mock import MagicMock, patch
response_create_stack = {...}
@patch('boto3.client')
def test_my_function(self, mock_boto3_client):
mock_create = MagicMock()
mock_create.create_stack.return_value =
response_create_stack
mock_boto3_client.return_value = mock_create
... |
from mock import MagicMock, patch
response_create_stack = {...}
response_describe_stacks = {...}
@patch('boto3.client')
def test_my_function(self, mock_boto3_client):
mock_create = MagicMock()
mock_create.create_stack.return_value =
response_create_stack
mock_update = MagicMock()
mock_update.describe_stacks.return_value =
response_describe_stacks
mock_boto3_client.side_effect = [mock_create,
mock_update]
... |
- Nested patches:
@patch('path.to.first')
@patch('path.to.second')
def test_toto(self, mock_second, mock_first)
- Problemes / Problems
@patch(" my_dir.my_file.MyClass ")
ModuleNotFoundError: No module named
'my_dir.my_file.MyClass'; 'my_dir.my_file' is not a
package
- should be:
@patch(" my_dir.my_file.my_function ")
- ...
- "django.db.utils.InterfaceError: connection already
closed"
- Solution:
- restart database service?
- ...
- ...
|
REST
|
- REST client
- Simple REST server
- views.py
from django.http import
HttpResponse
from django.http import Http404
from django.views.decorators.csrf import csrf_exempt
import commands
@csrf_exempt
def toto(request):
if request.method != 'POST':
raise
Http404
cadena = request.POST['cadena']
comanda = 'ls -l ' + cadena
status, output =
commands.getstatusoutput(comanda)
return HttpResponse(output)
- Django
REST framework
- tomchristie /
django-rest-framework
(Github)
- Django
REST Framework-Specific Standards
- Documentation
- Django
REST
Framework (Google group)
- Django
REST framework 3.6
- Django
REST framework 3.5
Creating a ModelSerializer without either
the 'fields' attribute or the 'exclude' attribute
has been deprecated
since 3.3.0, and is now disallowed. Add an
explicit fields = '__all__' to the ...
serializer."
class
MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields =
'__all__'
class
MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields =
('field1',)
- Django
REST
framework 3.1
- Pagination
- Versioning
- i18n
- New field types
- ModelSerializer API
- Moving packages out of core
- REST
Framework
3.0
- 3.0
pre-release
discussion
- Changes:
2.x
|
<3.12
Django
REST
framework 3.0
|
3.12 |
- request.DATA
- request.POST
- request.FILES
|
|
|
|
|
|
- serializer.object.toto
- serializer.object.toto=value
- serializer.save()
|
Use
.validated_data
instead of .object
- serializer.validated_data['toto']
- serializer.save(toto=value)
|
|
- django.core.exceptions.ValidationError
|
- from rest_framework import serializers
serializers.ValidationError
|
|
- def validate_<field_name>(self,
attrs, source)
|
- def validate_<field_name>(self,
attrs)
|
|
model:
|
Differences
between
ModelSerializer validation and ModelForm
serializer:
|
|
|
Removal
of
validate() (field classes)
|
|
|
Changes
to
pre/post save hooks.
- perform_create(self, serializer)
- perform_update(self, serializer)
|
|
- def restore_object(self, attrs,
instance=None)
|
- def create(self, validated_data)
- def update(self, instance,
validated_data)
|
|
Writable
nested serialization:
|
Writable
nested
serialization:
- depth => read_only
- def create()
- def update()
|
|
|
print(serializer)
|
|
|
|
|
|
|
|
HyperlinkedModelSerializer:
|
HyperlinkedModelSerializer
- extra_kwargs
= {
'url':
{'lookup_field': 'uuid',
'view_name':'mymodel-detail'}
}
|
|
- Field(source='function_in_model_class')
|
Fields
for
model methods and properties (read
only):
- fields( ...,
'function_in_model_class')
|
|
|
|
|
|
BaseSerializer
- basic:
- .data
- .is_valid()
- .errors
- .save()
- can be overriden:
- .to_representation()
- .to_internal_value()
- .create(), .update()
|
|
- Field (read-only)
- WritableField (read, write)
|
- Field
- ReadOnlyField (read-only)
|
|
|
Arguments:
The
required,
allow_null, allow_blank and default
arguments.
|
|
|
ListField
|
|
ChoiceField
- color = ChoiceField(choices=(('red','red
colour'),('green','green colour')))
|
ChoiceField
- color =
ChoiceField(choices=(('red','red
colour'),('green','green colour')))
- color =
ChoiceField(choices=['red','green'])
|
|
|
MultipleChoiceField
|
|
- to_native(self, data)
(deserialization/write)
- from_native(self, value)
(serialization/read)
|
Changes
to
the custom field API.
- to_internal_value(self, data)
(deserialization/write)
- to_representation(self, value)
(serialization/read)
|
|
- field_from_native()
- field_to_native()
|
|
|
|
Relational
fields
|
|
SerializerMethodField
- my_field =
serializers.SerializerMethodField('get_myfield')
def get_myfield(self, obj)
...
|
SerializerMethodField
- my_field =
serializers.SerializerMethodField()
def get_myfield(self, obj)
...
|
|
|
UniqueValidator
- validators=UniqueValidator(queryset=...)
UniqueTogetherValidator
- default_validators=[UniqueTogetherValidator(queryset=...,
fields=('field1','field2'))]
|
|
|
Generic
Views
|
|
|
serializers.NullBooleanField |
serializers.BooleanField
with allow_null=True [#7122] |
- Library: parsers,
renderers,
...
- Tutorial
- API Guide
- Instal·lació
/ Installation
- virtualenv +
pip (e.g. for deployment with Apache)
# virtualenv -p python26
/opt/PYTHON26
# chown apache.apache -R /opt/PYTHON26
# source /opt/PYTHON26/bin/activate
# pip install Django==1.4.3
djangorestframework==2.2.5 python-dateutil rfc3339
# deactivate
- virtualenv +
pip (from git)
pip install -e
git+git://github.com/tomchristie/django-rest-framework.git#egg=django-rest-framework
- easy_install
# easy_install -U
distribute
# easy_install python-dateutil
# easy_install rfc3339
# easy_install [--upgrade] djangorestframework
- Configuració
/ Setting
- settings.py
INSTALLED_APPS
=
(
...
'rest_framework', )
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES':
('rest_framework.permissions.IsAdminUser',),
'PAGINATE_BY': 10,
...
}
- data
/ date
- settings.py
- si no s'especifiquen a settings.py, els formats de
dates i temps depenen del renderer (per exemple, el
renderer xml no fa iso-8601 per defecte)
- si fa iso-8601, la precisió ha de ser de
microsegons (%06N) i no de nanosegons
- si s'especifica a settings, sempre es pot
sobreescriure a serializers.py
start_date =
serializers.DateTimeField(format='%Y-%m-%d
%H:%M:%S')
start_time =
serializers.TimeField(format='%H:%M:%S.%f')
- deserialization (e.g. from tests)
from django.utils
import
timezone
now = timezone.now()
begin = now + datetime.timedelta(minutes=5)
url = reverse('mymodel-list')
data = {'begin': begin}
response = self.client.post(url, data,
format="json")
- filter
- Migration from old version to new one:
from rest_framework.renderers import
BaseRenderer
#from rest_framework.renderers import
DEFAULT_RENDERERS
from rest_framework.settings import DEFAULTS
class UriListRenderer(BaseRenderer):
media_type = 'text/uri-list'
format = 'uri-list'
...
class TotoView(View):
...
renderers =
DEFAULTS['DEFAULT_RENDERER_CLASSES'] +
(UriListRenderer,)
...
- Recursivitat /
Recursivity
- Biblioteques
relacionades / Related libraries
- Paginació / Pagination
- Documenting
your
API
- Swagger
- The
browsable API
- Disable
- urls.py
urlpatterns = [
#url(r'^api-auth/',
include('rest_framework.urls',
namespace='rest_framework')),
- settings.py
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
#'rest_framework.renderers.BrowsableAPIRenderer',
),
- drf-spectacular
- Swagger
- supports i18n
- newest version for Django 1.x:
pip install
"drf-spectacular==0.8.5"
- From
drf-yasg to OpenAPI 3
- Problemes / Problems
TypeError: get_operation() takes 3
positional arguments but 4 were given
- drf-yasg
-
Functional
overview |
path params |
query params |
post params |
result |
section name in
/docs/ |
Parameters |
Responses |
Parameters (path) |
Parameters (query) |
Parameters (body) |
generated for methods |
|
|
|
|
default is generated from |
- url pattern parameters
- queryset
- lookup_field
|
- filter_backends
- paginator
|
|
|
overwritten by
@swagger_auto_schema |
|
|
|
|
- Swagger
- Custom
schema generation
- The
@swagger_auto_schema decorator
- drf_yasg.utils.swagger_auto_schema
- Exemples / Examples:
from
drf_yasg.utils import
swagger_auto_schema
@swagger_auto_schema(deprecated=True)
from
drf_yasg.utils import
swagger_auto_schema
@swagger_auto_schema(responses={400:
"Something bad",500: "Something really
bad"})
@action...
def ...
from
drf_yasg.utils import
swagger_auto_schema
from drf_yasg import openapi
class ...
test_param =
openapi.Parameter('test',
openapi.IN_QUERY, description="Put
something here",
type=openapi.TYPE_STRING)
@swagger_auto_schema(manual_parameters=[test_param])
@action ...
from
drf_yasg.utils import
swagger_auto_schema
class
MyModelViewSet(viewsets.ModelViewSet):
...
serializer_class =
MyModelSerializer
input_serializer_class =
MyModelInputSerializer
@swagger_auto_schema(request_body=MyModelInputSerializer)
def create(self,
request, *args, **kwargs):
return
viewsets.ModelViewSet.create(self,
request, *args, **kwargs)
...
def
get_serializer_class(self):
# TODO: OPTIONS?
if self.request.method == 'GET':
return self.serializer_class
else:
# POST, PATCH
return self.input_serializer_class
- Error responses
from
rest_framework import status @swagger_auto_schema(
...
responses = {
status.HTTP_400_BAD_REQUEST: "Something is
bad",
}
)
- DRF error responses
- Output serializer,
but no input serializer:
from
drf_yasg.utils import swagger_auto_schema,
no_body
@swagger_auto_schema(request_body=no_body)
- Different serializer classes
- Problemes / Problems
- "AssertionError: Serializer class or
instance required, not unicode"
- Solució / Solution
- make sure that you do not have
"from __future__ import
unicode_literals" on top of your
file
- see
SwaggerAutoSchema.get_response_schemas(),
where a check against str should
succeed, but it fails because
provided string is a unicode:
- if isinstance(serializer,
str):
- Support
for SerializerMethodField
- drf_yasg.utils.swagger_serializer_method
- Exemples / Examples
from
drf_yasg.utils import
swagger_serializer_method
class
MyModeSerializer(serializers.Serializer):
myfield =
serializers.SerializerMethodField()
@swagger_serializer_method(serializer_or_field=MyFieldSerializer)
def
get_myfield(self, obj):
return MyFieldSerializer().data
from
drf_yasg.utils import
swagger_serializer_method
class
MyModeSerializer(serializers.Serializer):
myfields =
serializers.SerializerMethodField()
@swagger_serializer_method(serializer_or_field=MyFieldSerializer(many=True))
def
get_myfields(self, obj):
return
MyFieldSerializer(many=True).data
- DRF
Error Responses #224
- Django
REST Swagger (deprecated)
- Swagger
- Documentation
(0.3)
- Instal·lació / Installation
pip install django-rest-swagger
- from git:
pip install -e
git+git://github.com/marcgibbons/django-rest-swagger#egg=django-rest-swagger
- settings.py
INSTALLED_APPS = (
...
'rest_framework_swagger',
...
)
- version 2.0
- IMPORTANT: els mètodes que necessitin
autenticació només apareixeran si feu
«Authorize»
- views.py
- urls.py
from
rest_framework.schemas import
get_schema_view
from rest_framework_swagger.renderers
import OpenAPIRenderer, SwaggerUIRenderer
schema_view = get_schema_view(
title='my API',
renderer_classes=[OpenAPIRenderer,
SwaggerUIRenderer]
)
urlpatterns = [
# django-rest-swagger
0.3 documentation
#url(r'^docs/',
include('rest_framework_swagger.urls')),
# django-rest-swagger
2.0 documentation
url(r'^docs/',
schema_view),
- version 0.3
- my_project/urls.py
urlpatterns = [
# swagger 0.3
documentation
url(r'^docs/',
include('rest_framework_swagger.urls')),
...
]
- Static documentation
- Prioritat / Priority
- YAML in views.py
- help_text in serializers.py (if field is
explicitely added in serializer, help_text is
used, and if it is not specified, no final
comment will be shown)
- help_text in models.py
- Problemes / Problems
Unable to read api 'v1' from path
http://127.0.0.1:8000/docs/api-docs/v1 (server
returned INTERNAL SERVER ERROR)
- To debug: open the conflicitve path with a
browser
- POST entry points show parameters as a single
json, instead of form fields than can be filled
by user
- Solució / Solution
- FormParser must be the first one in
DEFAULT_PARSER_CLASSES
- settings.py
REST_FRAMEWORK
= {
...
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.FormParser',
'rest_framework.parsers.JSONParser',
'watchit_manager.parsers.PhpMultiPartParser',
),
- HttpRequest has not query_params
- Solution: when using get_serializer_class,
take into account that swagger is accessing
as HttpRequest instead of usual
rest_framework.request.Request
- views.py
from
distutils.util import strtobool
from django.http import
HttpRequest
class
MyModelViewSet(viewsets.ModelViewSet):
"""
My help
---
retrieve:
parameters:
- name: extended
description:
"Whether to return an extended
description"
paramType:
query
type:
boolean
"""
queryset =
MyModel.objects.all()
serializer_class =
MyModelSimpleSerializer
lookup_field =
'name'
def
get_serializer_class(self):
# with extended flag:
# true: returns extended
information
# false: returns basic information
# for swagger
if isinstance(self.request,
HttpRequest):
return
MyModelExtendedSerializer
else:
show_extended
= strtobool(
self.request.query_params.get('extended','False')
)
if show_extended:
return
MyModelExtendedSerializer
else:
return
MyModelSimpelSerializer
- serializers.BaseSerializer not working
- serializers.ListField(
child=serializers.IntegerField()
- Boolean
- boolean
type
doesn't work properly #340
- Solution
- views.py
import
django_filters
BOOLEAN_CHOICES = (('false',
'False'), ('true', 'True'),)
class
MyModelFilterSet(django_filters.FilterSet):
# must be
specified to allow "true" and
false in lower case
is_free =
django_filters.TypedChoiceFilter(choices=BOOLEAN_CHOICES,
coerce=strtobool,
help_text=_("Whether
the object is free"))
- Filter query parameters
- YAML
Docstring
- 4.3.3
Data
type fields
- How
to
show query parameter options in Django REST
Framework - Swagger
- Syntax
-
yaml
|
class
MyModelFilterSet(django_filters.FilterSet):
|
gui
|
top
level
|
first
level
|
key
|
value
|
description
|
field_1
= django_filters.XXFilter(...)
|
Response
class
|
Parameters
|
Error
Status Codes
|
with
ViewSet |
with
generics.xxxAPIView
|
|
|
|
|
|
|
|
|
|
create:
list:
retrieve:
update:
partial_update:
destroy:
|
GET:
(bug)
POST:
PATCH:
PUT:
|
serializer:
|
|
|
my_app.serializers.MyModelSerializer
|
|
|
x
|
|
|
request_serializer:
|
|
|
...
|
|
|
?
|
|
|
response_serializer:
|
|
|
...
|
|
|
x
|
|
|
type:
|
|
required:
|
true
|
when
not using serializer, fields can be
directly described here
|
|
x
|
|
|
false
|
|
|
|
|
type:
|
string
|
|
|
|
|
integer
|
|
|
|
|
boolean
|
|
|
|
|
url
|
|
|
|
|
...
|
|
|
|
|
parameters:
(Parameter
Object)
(Data
type fields) |
|
-
name:
|
my_name
|
|
|
|
Parameter
|
|
description: |
my_description
"my
desription
with: colons"
|
|
label=
|
|
Description
|
|
paramType:
|
query
|
get
parameters: ?foo=...&bar=... |
|
|
Parameter
Type
|
|
form |
post
parameters
|
|
|
|
body |
all
parameters (in json?)
|
|
|
|
file |
file
to upload
|
|
|
|
...
|
|
|
|
|
pytype: |
my_app.serializers.MoneySerializer
..serializers.MoneySerializer
|
|
|
|
x
|
|
required: |
true
|
|
|
|
(bold
face)
|
|
false
|
|
|
|
(roman
face)
|
|
type:
|
string
|
|
|
|
Data
Type
|
|
integer
|
|
|
|
|
boolean
|
|
|
|
|
url
|
|
|
|
|
...
|
|
|
|
|
type:
array collectionFormat:
csv
items:
type: string |
|
|
|
|
type:
choice
enum:
- value_1
- value_2
- ...
|
|
|
|
|
parameters_strategy
|
|
|
|
|
|
|
|
|
omit_parameters:
|
|
-
query
|
|
|
|
|
|
|
-
path
|
|
|
|
|
|
|
...
|
|
|
|
|
|
|
responseMessages:
|
|
-
code:
|
403
...
|
|
|
|
|
HTTP
Status Code |
message: |
My
message
|
|
|
|
|
Reason
|
|
|
|
|
|
|
|
widget
|
|
Value
Data Type
|
|
- Query parameters for GET:
- http://.../?foo=...&bar=...
class
MyModelViewSet(viewsets.ModelViewSet):
"""
Your docs.
---
# YAML (must be
separated by '---')
# if using colons in
descriptions, put all the description
inside quotes
retrieve:
parameters:
- name: detail
description:
boolean to indicate whether to show
specific details
paramType:
query
"""
@detail_route()
def foo_view(self,
request, **kwargs):
"""
Your
docs.
---
# YAML (must be
separated by '---')
# if
using colons in descriptions, put all the
description inside quotes
parameters:
-
name: foo
description: description of foo parameter
paramType: query
-
name: bar
description:
"description of bar (e.g.: toto)"
paramType: query
responseMessages:
- code: 400
message:
Your message about this code
"""
- Filter query
parameters: show them only at GET
- views.py
class
MyModelViewSet(viewsets.ModelViewSet):
"""
My help
---
create:
omit_parameters:
-
query
retrieve:
omit_parameters:
-
query
update:
omit_parameters:
-
query
partial_update:
omit_parameters:
-
query
destroy:
omit_parameters:
-
query
- Headers (*)
parameters:
-
name: Authorization
paramType: header
required: true
- Override serializer by a simple description
- Choices
- Using the string label instead of integer
value
- views.py
class ...
"""
---
parameters:
- name: my_choice_field_1
description: "My description"
paramType: form
type: choice
enum:
- my_value_option_1
- my_value_option_2
"""
- List, array
- Swagger
UI doesn't seem to support Arrays yet?
#982
- With yuml (not working)
- views.py
class
MyModelViewSet(viewsets.ModelViewSet):
"""
My help
---
create:
parameters:
- name: prices
description:
"Array of prices"
type:
array
items:
type:
string
collectionFormat:
csv
- Simple serializer in a detail_route
- Option 1 (recommended one) Serializer
specification is also needed in yaml
description
- views.py
from
my_app.serializers import MyDetailSerializer
class
MyModelViewSet(viewsets.ModelViewSet):
serializer_class=MyModelSerializer
...
@detail_route(serializer_class=MyDetailSerializer)
def
status(self, request, **args,
**kwargs):
"""
My help.
---
serializer: MyDetailSerializer
"""
input_data = dict()
input_data['field1'] = 0
input_data['field2'] = 1
# serializer specified inside the
decorator
serializer =
self.get_serializer(data=input_data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data)
- serializers.py
class
MyDetailSerializer(serializers.Serializer):
field1 =
serializers.IntegerField()
field2 =
serializers.IntegerField()
- Option 2
- views.py
# even if
not used MyDetailSerializer must
be included in order to
drf-swagger to work
from my_app.serializers import MyDetailSerializer
class
MyModelViewSet(viewsets.ModelViewSet):
...
@detail_route()
def
status(self, request, **args,
**kwargs):
"""
My help.
---
serializer: MyDetailSerializer
"""
...
- serializers.py
class
MyDetailSerializer(serializers.Serializer):
field1 =
serializers.IntegerField()
field2 =
serializers.IntegerField()
- File upload
- (?) Not working! Even if paramType
is specified as form, in the GUI it shows as
body, and any request gives a "403
undefined" error:
- views.py
class
MyModelViewSet(viewsets.ModelViewSet):
"""
My help.
---
create:
parameters:
- name: thumbnail
description:
Thumbnail image to upload
type:
file
paramType:
form
consumes: multipart/form-data
- Nested writable serializers
- Autenticació / Authentication
- Tutorial
4:
Authentication and permissions
- API
Guide:
Authentication
-
|
settings.py
|
credentials
when successfully authenticated
|
client
|
|
REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES
|
INSTALLED_APPS
|
request-user
|
request.auth
|
curl |
requests
|
built-in
|
rest_framework.authentication.BasicAuthentication
|
|
User
|
None
|
curl -X GET
-u admin:abc123
http://127.0.0.1:8000/api/example/
|
import requests
from requests.auth import HTTPBasicAuth
r =
requests.get('http://127.0.0.1:8000/v1/api/totos/',
auth=HTTPBasicAuth('admin', 'abc123'))
|
rest_framework.authentication.TokenAuthentication |
rest_framework.authtoken
|
User |
rest_framework.authtoken.models.BasicToken
|
curl -X GET
http://127.0.0.1:8000/api/example/ -H
'Authorization: Token <admin_token>'
|
|
rest_framework.authentication.SessionAuthentication |
|
User |
None |
|
|
3rd party
|
DigestAuthentication
|
|
|
|
curl -X GET --digest
-u admin:abc123
http://127.0.0.1:8000/api/example/ |
import requests
from requests.auth import HTTPDigestAuth
r =
requests.get('http://127.0.0.1:8000/v1/api/totos/',
auth=HTTPDigestAuth('admin', 'abc123')) |
JWT
|
rest_framework_jwt.authentication.JSONWebTokenAuthentication
|
|
|
|
curl -X POST
-d "username=admin&password=abc123"
http://localhost:8000/api-token-auth/
curl -X GET
http://127.0.0.1:8000/api/example/ -H
'Authorization: JWT <your_token>'
|
import requests
import json
payload = {'username': 'admin', 'password':
'abc123'}
r1 =
requests.post('http://127.0.0.1:8000/api-token-auth/',
headers={'Content-Type':'application/json'},
data=json.dumps(payload) )
token = r1.json()['token']
r2 =
requests.get('http://127.0.0.1:8000/v1/api/totos/',
headers={'Authorization':'JWT %s'%token})
|
- JWT
- djangorestframework-simplejwt
- signing algorithm
-
signing |
ALGORITHM |
SIGNING_KEY |
VERIFYING_KEY |
HMAC signing (symmetric) |
HS256, HS384, HS512 |
secret string (defaults to
SECRET_KEY) |
SIGNING_KEY |
RSA signing (asymetric) |
RS256, RS384, RS512 |
RSA private key (>2048
bits) |
RSA public key |
- login view (/api/token) returns two jwt
tokens (signed with algorithm specified by
ALGORITHM; with secret/private key specified
by SIGNING_KEY (default is SECRET_KEY)):
- refresh
- header:
{"typ":"JWT",
"alg":"HS256"}
- payload:
{"token_type":"refresh",
"exp":..., "iat":..., "jti":...,
"user_id":...}
- access
- header:
{"typ":"JWT",
"alg":"HS256"}
- payload:
{"token_type":"access",
"exp":..., "iat":..., "jti":...,
"user_id":...}
- other views, (including /api/verify)
accessed with specific header:
-H 'Authentication Bearer my_access_jwt_token'
- token is verified using secret/public
key specified by VERIFYING_KEY
- django-rest-framework-jwt
- djangorestframework-jwt
pip install djangorestframework-jwt
- my_project/my_project/settings.py
REST_FRAMEWORK
= {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
}
JWT_AUTH
= {
...
}
- my_project/my_project/urls.py
urlpatterns =
patterns('',
# ...
url(r'^api-token-auth/',
'rest_framework_jwt.views.obtain_jwt_token'),
)
- Token expiration
- my_project/my_project/settings.py
JWT_AUTH =
{
'JWT_EXPIRATION_DELTA':
datetime.timedelta(seconds=3600),
}
- Token refresh and verify
- my_project/my_project/settings.py
JWT_AUTH =
{
'JWT_ALLOW_REFRESH': True,
}
- my_project/my_project/urls.py
urlpatterns
= patterns('',
url(r'^api-token-verify/',
'rest_framework_jwt.views.verify_jwt_token'),
url(r'^api-token-refresh/',
'rest_framework_jwt.views.refresh_jwt_token'),
)
- Where
can
I override jwt_response_payload_handler
method?
- my_project/settings.py
JWT_AUTH
= {
'JWT_RESPONSE_PAYLOAD_HANDLER':
'my_app.utils.jwt_response_payload_handler',
}
- my_app/utils.py
from
my_app.serializers import
UserSerializer
def
jwt_response_payload_handler(token,
user=None, request=None):
return {
'token': token,
'user': UserSerializer(user).data
}
- test
- using slumber (NOTE: does not work
with '-' in urls)
- import slumber
a =
slumber.API("http://127.0.0.1:8000/")
a.apitokenauth.post({"username":"admin",
"password":"admin"})
- ...
- ...
- Filtratge
/ Filtering
-
|
URL
|
settings.py
|
urls.py
|
views.py
|
filters.py
|
Against the
current user
|
http://example.com/api/purchases/ |
|
|
class
MyModelViewSet(viewsets.ModelViewSet)
def get_queryset(self)
user
= self.request.user
return
Purchase.objects.filter(purchaser=user)
|
|
Against the URL
|
http://example.com/api/purchases/denvercoder9 |
|
url('^purchases/(?P<username>.+)/$',
PurchaseList.as_view()),
|
class
MyModelViewSet(viewsets.ModelViewSet)
def get_queryset(self)
username = self.kwargs['username']
return
Purchase.objects.filter(purchaser__username=username)
|
|
Against query
parameters
|
http://example.com/api/purchases?username=denvercoder9 |
|
|
class
MyModelViewSet(viewsets.ModelViewSet)
def get_queryset(self)
queryset = Purchase.objects.all()
username = self.request.QUERY_PARAMS.get('username',
None)
if
username is not None:
queryset
= queryset.filter(purchaser__username=username)
return queryset
|
|
Generic
filtering (simple field)
|
http://example.com/api/purchases?type=denvercoder9
|
REST_FRAMEWORK
= {
'DEFAULT_FILTER_BACKENDS':
('rest_framework.filters.DjangoFilterBackend',),
}
|
|
# from
rest_framework.filters import
DjangoFilterBackend
class PlaceViewSet(viewsets.ModelViewSet):
# if not specified in
DEFAULT_FILTER_BACKENDS:
# filter_backends =
(DjangoFilterBackend,)
# DjangoFilterBackend
filter_fields
= ('type',)
|
|
Generic
filtering (FilterSet)
|
|
# from
rest_framework.filters import
DjangoFilterBackend
import django_filters
class MyFilterSet(django_filters.FilterSet):
...
class PlaceViewSet(viewsets.ModelViewSet):
# if not specified in
DEFAULT_FILTER_BACKENDS:
# filter_backends =
(DjangoFilterBackend,)
# DjangoFilterBackend
filter_class
= MyFilterSet
|
class IsoDateTimeRangeField(django_filters.fields.RangeField):
...
class
BooleanMethodFilter(django_filters.MethodFilter):
...
|
Search
|
http://...?search=...
|
REST_FRAMEWORK =
{
'DEFAULT_FILTER_BACKENDS':
('rest_framework.filters.SearchFilter',),
} |
|
# from
rest_framework.filters import SearchFilter
class PlaceViewSet(viewsets.ModelViewSet):
# if not specified in
DEFAULT_FILTER_BACKENDS:
# filter_backends =
(SearchFilter,)
# SearchFilter: search at any
place (@) in field name:
search_fields = ('@name',) |
|
Ordering
|
http://...?ordering=-field_a
|
|
|
from
rest_framework.filters import OrderingFilter
class MyModelViewSet(viewsets.ModelViewSet)
filter_backends =
(OrderingFilter,)
# OrderingFilter: fields may
be ordered against
ordering_fields
= ('field_a','field_b',)
# OrderingFilter: default
ordering
ordering = ('-field_c',)
|
|
Custom
|
|
|
|
from
rest_framework.filters import BaseFilterBackend
class IsOwnerFilterBackend(filters.BaseFilterBackend):
"""
Filter that only
allows users to see their own objects.
"""
def filter_queryset(self, request, queryset, view):
return
queryset.filter(owner=request.user)
class MyModelViewSet(viewsets.ModelViewSet)
filter_backends = (IsOwnerFilterBackend,)
|
|
- Generic
filtering
- django-filter
(used by DjangoFilterBackend)
pip install django-filter
- Migrating
to 1.0
- my_project
- my_project
- settings.py
REST_FRAMEWORK
= {
'DEFAULT_FILTER_BACKENDS':
('rest_framework.filters.DjangoFilterBackend',),
}
- my_app
- views.py
class
PlaceViewSet(viewsets.ModelViewSet):
filter_fields
= ('type',)
- ForeignKey Slug
- models.py
- Option 1:
- views.py
class
BookViewSet(viewsets.ModelViewSet):
filter_fields
= ('publisher__name',)
- url
http://localhost:8000/v1/api/books/?publisher__name=...
- Option 2 (without double underscore):
- views.py (>=1.0)
class
BookFilterSet(django_filters.FilterSet):
publisher =
django_filters.CharFilter(method='filter_publisher')
def
filter_publisher(self, queryset,
name, value):
qs =
queryset.filter(publisher__name__iexact=value)
return qs
class Meta:
model = NotificationSetting
fields = ['publisher',]
class
BookViewSet(viewsets.ModelViewSet):
filter_class
= BookFilterSet
- views.py (<1.0)
class
BookFilterSet(django_filters.FilterSet):
publisher =
django_filters.MethodFilter(action='get_publisher')
def
get_publisher(self, queryset,
value):
qs =
queryset.filter(publisher__name__iexact=value)
return qs
class Meta:
model = NotificationSetting
fields = ['publisher',]
class
BookViewSet(viewsets.ModelViewSet):
filter_class
= BookFilterSet
- url
http://localhost:8000/v1/api/books/?publisher=...
- Multiple
values
- DjangoFilterBackend
with
multiple ids
- Logical
OR on multiple values for a CharField
#1076
- options
- BaseInFilter
- example
class
CharInFilter(BaseInFilter,
CharFilter):
pass
class
MyModelFilterSet(FilterSet):
#
django-filters 1.x
# it calls
Filter.filter()
myfield =
CharInFilter(name='myfield')
# it calls
filter_myfields()
#myfield =
CharInFilter(label='myfields',
method='filter_myfields',
help_text=_('Filter by
multiple myfield'))
#
django-filters 2.x
#
it calls Filter.filter()
#
parameter lookup_expr is no
needed, as
lookup_expr='in'
is the default for
BaseInFilter
#myfield =
CharInFilter(field_name='myfield',
lookup_expr='in')
class
Meta(object):
model = MyModel
- MultipleChoiceField with disabled
validation
- Logical
OR on multiple values for a
CharField #1076
- "Out of the box, the only filters
that support
&site=...&site=... are the
multiple choice filters."
- example
import
django_filters
from django import forms
class
NonValidatingMultipleChoiceField(forms.MultipleChoiceField):
def
validate(self, value):
pass
# Or inherit
`filters.AllValuesMultipleFilter`
class
NonValidatingMultipleChoiceFilter(django_filters.MultipleChoiceFilter):
field_class
=
NonValidatingMultipleChoiceField
class
MyModelFilterSet(FilterSet):
#
django-filters 1.x
# it calls
MultipleChoiceFilter.filter()
# OR with
provided values (default)
myfield = NonValidatingMultipleChoiceFilter (name='myfield')
# AND with
provided values
(conjoined=True)
myfield_and
=
NonValidatingMultipleChoiceFilter (name='myfield',
conjoined=True)
- ...
- IsoDateTime
- Filter
on
range of datetimes in django rest
framework
- my_app/models.py
class
MyModel(models.Model):
begin =
models.DateTimeField()
- my_app/filters.py
import
django_filters
# we need to redefine it because
DateTimeRangeField does not work
correctly with ISO datetime
(YYYY-MM-DDThh:mm:ss.mmmmmmZ)
class IsoDateTimeRangeField(django_filters.fields.RangeField):
def __init__(self,
*args, **kwargs):
fields = (
django_filters.fields.IsoDateTimeField(),
django_filters.fields.IsoDateTimeField())
super(IsoDateTimeRangeField,
self).__init__(fields, *args,
**kwargs)
def compress(self,
data_list):
if data_list:
start_datetime,
stop_datetime = data_list
return
slice(start_datetime, stop_datetime)
return None
class IsoDateTimeFromToRangeFilter(django_filters.RangeFilter):
field_class = IsoDateTimeRangeField
- my_app/views.py
import
django_filters
from my_app.models import MyModel
from my_app.filters import
IsoDateTimeFromToRangeFilter
class
MyModelFilter(django_filters.FilterSet):
begin = IsoDateTimeFromToRangeFilter()
class Meta:
model = MyModel
fields = ['begin',]
class
MyModelViewSet(viewsets.ModelViewSet):
...
filter_class =
MyModelFilter
- Ús / Usage
http://localhost:8000/v1/api/mymodels/?begin_0=2015-10-31T16:00:00Z&begin_1=2015-10-31T16:31:00Z
- MethodFilter
- IMPORTANT: MethodFilter
and Filter.action replaced by
Filter.method
- How
use
MethodFilter from django-filter app?
- my_app/models.py
class
MyModel(models.Model):
begin =
models.DateTimeField(_("Begin"),
help_text=_("Begin date"))
end =
models.DateTimeField(_("End"),
help_text=_("End date"))
- my_app/views.py
import
datetime
import django_filters
class
MyModelFilter(django_filters.FilterSet):
next =
django_filters.MethodFilter(action='get_next')
def get_next(self,
queryset,
value):
"""
Filter the objects that begin between
now and (now+delta).
"""
qs = queryset
if value != '':
# get hours, minutes and seconds from
query (hh:mm:ss)
h,m,s
= ( int(d) for d in value.split(':') )
delta
= datetime.timedelta( hours=h,
minutes=m, seconds=s )
# get now in UTC
now = timezone.now()
qs = queryset.filter( begin__gte=now,
begin__lte=now+delta)
return qs
class Meta:
model = MyModel
fields = ['next',]
- Ús / Usage
- list of objects that begin within the
next 10 minutes and 20 seconds:
http://127.0.0.1:8000/v1/api/mymodels/?next=00:10:20
- Boolean
(lower case)
- my_app/models.py
class
MyModel(models.Model):
is_ok =
models.BooleanField(default=True)
- my_app/views.py
from
distutils.util import strtobool
i mport django_filters from
my_app.models import MyModel
BOOLEAN_CHOICES = (('false',
'False'), ('true', 'True'),)
class
MyModelFilter(django_filters.FilterSet):
# must be specified
to allow "true" and false in lower
case
is_ok =
django_filters.TypedChoiceFilter(choices=BOOLEAN_CHOICES,
coerce=strtobool)
class Meta:
model = MyModel
fields = ['is_ok',]
- Ús / Usage
http://localhost:8000/v1/api/mymodels/?is_active=true
- MethodFilter
with boolean
- IMPORTANT: MethodFilter
and Filter.action replaced by
Filter.method
- my_app/models.py
class
MyModel(models.Model):
begin =
models.DateTimeField(_("Begin"),
help_text=_("Begin date"))
end =
models.DateTimeField(_("End"),
help_text=_("End date"))
- my_app/filters.py
i mport
django_filters
class
BooleanMethodFilter(django_filters.MethodFilter):
field_class =
forms.NullBooleanField
- my_app/views.py
from
my_app.filters import BooleanMethodFilter
BOOLEAN_CHOICES = (('false', 'False'),
('true', 'True'),)
class
MyModelFilterSet(django_filters.FilterSet):
is_active =
BooleanMethodFilter(action='get_active',
choices=BOOLEAN_CHOICES,
help_text=_("Filter by objects that
are active"))
def get_active(self,
queryset,
value):
# get now in UTC
now = timezone.now()
qs = queryset
if value != '':
value_bool
= strtobool(value)
if value_bool:
qs
= queryset.filter( begin__lte=now,
end__gte=now )
else:
qs
= queryset.filter( Q(begin__gte=now) |
Q(end__lte=now) )
return qs
class Meta:
model = MyModel
fields = ['is_active',]
- Choices
- models.py
class
TotoModel(models.Model):
FIELD_A_CHOICES
= (
(0,'first'),
...
)
field_a =
models.IntegerField(choices= FIELD_A_CHOICES)
- views.py
i mport
django_filters
from my_app.models import TotoModel
class
TotoFilter(django_filters.FilterSet):
field_a =
django_filters.ChoiceFilter(choices=TotoModel.FIELD_A_CHOICES,
help_text="Filter by field_a: %s" % ',
'.join([v for k, v in TotoModel.FIELD_A _CHOICES]))
def __init__(self,
data=None, queryset=None, prefix=None,
strict=None, request=None):
if 'field_a' in data:
field_a
= data['field_a']
# if it is not already a digit, invert
key/values
if not field_a.isdigit():
try:
inverted_choices
= dict((v, k) for k, v in
TotoModel.FIELD_A_CHOICES)
#
conversion to make it writable
data
= dict(data)
data['field_a']
= inverted_choices[field_a]
except:
pass
super(TotoFilter, self).__init__(data,
queryset, prefix, strict)
class Meta:
model = TotoModel
fields = ['field_a',]
class
TotoViewSet(viewsets.ModelViewSet):
queryset =
TotoModel.objects.all()
serializer_class =
TotoSerializer
filter_class =
TotoFilter
- Usage
http://.../totos?field_a=first
http://.../totos?field_a=0
- django-rest-framework-filters
- Usuaris / Users
- Recupera i
utilitza l'usuari de la petició / Get and use the user
from request
- Serialització
d'usuaris i grups / Users and groups serialization
- Extended user (with OneToOneField)
- Extended
user with django-rest-auth (USER_DETAILS_SERIALIZER)
- Django
Rest
Framework make OnetoOne relation ship feel like it
is one model
- models.py
class
Profile(models.Model):
user =
models.OneToOneField(User)
avatar_url =
models.URLField(default='',
blank=True) # e.g.
- serializers.py
class
UserSerializer(serializers.HyperlinkedModelSerializer):
# A field from the
user's profile:
avatar_url =
serializers.URLField(source='profile.avatar_url',
allow_blank=True)
class Meta:
model = User
fields = ('url', 'username', 'avatar_url')
def create(self,
validated_data):
profile_data =
validated_data.pop('profile', None)
user = super(UserSerializer,
self).create(validated_data)
self.create_or_update_profile(user,
profile_data)
return user
def update(self,
instance, validated_data):
profile_data =
validated_data.pop('profile', None)
self.create_or_update_profile(instance,
profile_data)
return super(UserSerializer,
self).update(instance, validated_data)
def
create_or_update_profile(self, user,
profile_data):
profile, created =
Profile.objects.get_or_create(user=user,
defaults=profile_data)
if not created and profile_data is not
None:
super(UserSerializer,
self).update(profile, profile_data)
- Requests
and
responses
- request (
curl
-H "Content-Type:..." )
- application/json
- application/x-www-form-urlencoded
- multipart/form-data
- response (
curl
-H "Accept: ..." )
- application/json
- text/html
- Exemples / Examples:
curl -H 'Accept: application/json; indent=4'
-u admin:password http://127.0.0.1:8000/users/
-
|
|
|
curl
|
python-requests
|
request
|
parser
|
JSONParser
|
-X
POST
-X PUT
-X PATCH
|
-H
"Content-Type:application/json"
|
-d
'{"key":"value"}' |
|
YAMLParser
|
-H
"Content-Type: application/yaml "
|
-d ... |
|
XMLParser
|
-H
"Content-Type: application/xml " |
-d ... |
|
FormParser
|
[-H
"Content-Type: application/x-www-form-urlencoded "] |
-d
'key=value' |
|
MultiPartParser
|
-H
"Content-Type: multipart/form-data " |
-d ... |
|
FileUploadParser
|
-H
"Content-Type: */* " |
-d ... |
|
response
|
renderer
|
JSONRenderer
|
-X GET
|
-H 'Accept:
application/json; indent=4' |
|
|
- Problem:
- >>> from rest_framework import renderers
- ImportError: User model is not to be found.
- (ImportError: Settings cannot be imported, because
environment variable DJANGO_SETTINGS_MODULE is
undefined.)
- Solution:
- Media
upload
- settings.py
# media files
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/mm/'
- my_app/models.py
class Cover(models.Model):
name
= models.CharField( max_length=100 )
media
= models.ImageField( upload_to='im', blank=True,
null=True)
def __unicode__(self):
return
u'%s' % (self.name)
- my_app/serializers.py
class
CoverSerializer(serializers.ModelSerializer):
class Meta:
model =
Cover
fields
= ('name','media',)
- my_app/views.py
from
rest_framework.parsers import JSONParser,
MultiPartParser
class CoverViewSet(viewsets.ModelViewSet):
queryset = Cover.objects.all()
serializer_class =
CoverSerializer
parser_classes = (JSONParser,
MultiPartParser,)
- client
- create a cover
- python-requests
import requests
from requests.auth import HTTPDigestAuth
username = 'admin'
password = 'admin'
payload = {'name':
'cover 6'}
r =
requests.post(server_url+'/v1/api/covers/',
auth=HTTPDigestAuth(username, password),
data=payload, files={'media':
open('test.png',
'rb')})
- Excepcions / Exceptions
- views.py
class
MyModelViewSet(viewsets.ModelViewSet):
def get_queryset(self):
# only
entries that belong to specified parent
uuid =
self.kwargs.get('uuid', None)
try:
parent_object = get_object_or_404(MyParentModel,
uuid=uuid)
list_objects =
MyModel.objects.filter(parent=parent_object)
return list_objects
except
Exception as e:
raise e
from django.http import Http404
class MyModelViewSet(viewsets.ModelViewSet):
def get_queryset(self):
# only
entries that belong to specified parent
uuid =
self.kwargs.get('uuid', None)
try:
parent_object = get_object_or_404(MyParentModel,
uuid=uuid)
list_objects =
MyModel.objects.filter(parent=parent_object)
return list_objects
except
Exception as e:
raise Http404
from rest_framework.exceptions
import APIException,
ValidationError,
ParseError
from rest_framework import status
from rest_framework.decorators import detail_route
class MyModelViewSet(viewsets.ModelViewSet):
...
def perform_create(self,
serializer):
try:
...
except
Exception as e:
...
# returned status code: 500
raise
APIException("Error
when creating my_model %s: %s" %
(serializer.validated_data['name'], e))
def perform_update(self,
serializer):
try:
...
except
Exception as e:
...
# returned status code: 400
raise
ValidationError("Error
when updating my_model %s: %s" %
(serializer.validated_data['name'], e))
...
try:
...
except
ValueError:
raise ParseError('Invalid
...')
@detail_route()
def my_function(self,
request,
**args, **kwargs):
try:
message
= ...
except
Exception as e:
return
Response({'detail':
'%s'%e},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return
Response({'response': message})
from
rest_framework.exceptions import APIException
class MyAPIException( APIException ):
status_code = 400
default_detail = 'Something is
not correct.' class
MyModelViewSet(viewsets.ModelViewSet):
...
def perform_create(self,
serializer):
try:
...
except
Exception as e:
...
raise
MyAPIException()
from rest_framework import
status from rest_framework import
generics
class
MyOtherModelCreateView(generics.CreateAPIView)
...
# based on CreateModelMixin
def create(self,
request,
*args, **kwargs):
serializer =
self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
try:
...
except
Exception as e:
return
Response({'detail':'%s'%e},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
headers
= self.get_success_headers(serializer.data)
return
Response(serializer.data,
status=status.HTTP_201_CREATED, headers=headers)
- serializers.py
class
MyModelSerializer(serializers.ModelSerializer):
def create(self,
validated_data):
try:
application,
created =
self.Meta.model.objects.get_or_create(**validated_data)
except
Exception as e:
logger.error("[MyModelSerializer.create] Error:
%s" % e)
raise e
return
application
from rest_framework import
serializers
from django.db.utils import IntegrityError
class
MyModelSerializer(serializers.ModelSerializer):
def create(self,
validated_data):
try:
application,
created =
self.Meta.model.objects.get_or_create(**validated_data)
except
IntegrityError:
logger.error("[MyModelSerializer.update]
IntegrityError")
raise
serializers.ValidationError({"my_field":["The
value of my_field must be
unique."]})
except
Exception as e:
logger.error("[MyModelSerializer.create] Error:
%s" % e)
raise
ValidationError("Error when
creating.")
return application
- and exception has to be captured at perform_create
in views.py ViewSet
- Resum
general / General summary: parser, deserialization,
validation, model, queryset, serialization, render
|
|
parser
|
deserialization
(write)
|
validation
|
model
|
queryset
|
serialization
(read)
|
render
|
definition
|
parsers.py
|
...
|
|
|
|
|
|
|
serializers.py |
|
class
PlaceSerializer(serializers.ModelSerializer):
def to_internal_value(self,
data):
...
class HexIntegerField(serializers.WritableField):
def to_internal_value(self,
data):
# convert
from hex in unicode ("0x02") to integer (2)
return
int(data, 16)
|
class
PlaceSerializer(serializers.ModelSerializer):
def validate(self,
attrs):
#
serializer.errors == {'non_field_errors': ['A non
field error']}
raise
serializers.ValidationError('A non field error')
def validate(self,
attrs):
#
serializer.errors == {'my_field': ['A field error']}
raise
serializers.ValidationError({'my_field': 'A field
error'})
def validate_<field_name>(self,
value):
...
|
def create():
...
def update():
...
|
|
class
PlaceSerializer(serializers.ModelSerializer):
def to_representation(self,obj):
...
class HexIntegerField(serializers.WritableField):
def to_representation(self,
obj):
return
"0x%02x" % (obj)
|
|
models.py
|
|
|
|
|
|
|
|
renders.py
|
|
|
|
|
|
|
...
|
usage
|
views.py
|
class
PlaceViewSet(viewsets.ModelViewSet):
parser_classes
= (JSONParser, MultiPartParser,)
|
class
PlaceViewSet(viewsets.ModelViewSet):
serializer_class
= PlaceSerializer |
|
|
class
PlaceViewSet(viewsets.ModelViewSet):
queryset
= Place.objects.all()
filter_fields
= ('category', 'in_stock')
|
class
PlaceViewSet(viewsets.ModelViewSet):
serializer_class
= PlaceSerializer |
class
PlaceViewSet(viewsets.ModelViewSet):
renderer_classes
= (BrowsableAPIRenderer, JSONRenderer, CSVRenderer)
|
def create(self,
request, *args, **kwargs):
data =
None
# data is
sent as direct json (literally or from file: @)
(JSONParser):
# curl -X
POST -H "Content-Type:application/json" -u
admin:admin http://127.0.0.1:8000/totos/ -d
'{"a":"b"}'
# curl -X
POST -H "Content-Type:application/json" -u
admin:admin http://127.0.0.1:8000/totos/ -d
@toto.json
if
request._content_type == 'application/json':
data = request.DATA
# data is
sent inside a file that is uploaded
(MultiPartParser):
# curl -X
POST -H "Content-Type:multipart/form-data" -u
admin:admin http://127.0.0.1:8000/totos/ -F
"file=@toto.json;type=application/json"
# note:
if type is not specified, it defaults to
"application/octet-stream"
else:
print
request.FILES['file']
fitxer
= File(request.FILES['file'])
content
= fitxer.read()
stream
= BytesIO(content)
if request.FILES['file'].content_type ==
'application/json':
input_data
= JSONParser().parse(stream)
else:
print
"No parser for content type: %s" %
(request.FILES['file'].content_type)
errors
= dict()
errors['error']
= "No parser for content type: %s" %
(request.FILES['file'].content_type)
return
Response(errors,status=status.HTTP_400_BAD_REQUEST)
fitxer.close()
# ... (follow to next column ->)
|
def get_serializer_class(self):
return
PlaceSerializer
def create(self,
request,
*args, **kwargs):
# ... (-> from
previous column)
#
serialize the data:
serializer = self.get_serializer(data=input_data,
files=request.FILES, many=True)
# if all
is fine, save the object in the database:
if
serializer.is_valid():
self.pre_save(serializer.object)
self.object
= serializer.save(force_insert=True)
self.post_save(self.object,
created=True)
return
Response(serializer.data,
status=status.HTTP_201_CREATED)
return
Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
|
|
|
def get_queryset(self):
return
MyModel.objects.filter(owner=self.request.user)
filter_class
= ProductFilter
class ProductFilter(django_filters.FilterSet):
...
|
|
|
actions.py
|
def
parse_mymodel_uploaded_file(uploaded_file,
content-type):
if content_type ==
'application/json':
fitxer =
File(uploaded_file)
content =
fitxer.read()
stream =
BytesIO(content)
input_data = JSONParser().parse(stream)
fitxer.close()
#
... (follow to next column ->) |
def
parse_mymodel_uploaded_file(uploaded_file,
content-type):
# ...
(-> from previous column)
serializer = MyModelSerializer(data=input_data,
many=True)
if
serializer.is_valid():
serializer.save(force_insert=True)
|
|
|
|
|
|
-
model
|
serializer
|
renderer
|
response
|
|
|
#
using default renderer
return Response(serializer.data)
|
class
MyModel(models.Model):
...
queryset = MyModel.filter(...)
|
class
MyModelSerializer(serializers.ModelSerializer):
class Meta:
model =
MyModel
fields =
('model_field_1',)
serializer = MyModelSerializer(queryset, many=True)
|
class
MyRenderer(BaseRenderer):
def render():
result =
...
return
result
content = MyRenderer().render(serializer.data)
|
...
|
-
|
class
MySimpleSerializer(serializers.Serializer):
field_1 = serializers.CharField()
field_2 = serializers.CharField()
field_3 = serializers.CharField()
obj_list = [{"field_1": "value_11",
"field_2": "value_12",
"field_3": "value_13",
},
{"field_1": "value_21",
"field_2": "value_22",
"field_3": "value_23",
},
]
serializer = MySimpleSerializer(obj_list, many=True) |
|
-
|
class
FlatListSerializer(serializers.Serializer):
def to_representation(self, obj):
result =
"·".join(obj)
return
result
class MySimpleSerializer(serializers.Serializer):
field_1 = FlatListSerializer()
field_2 = serializers.CharField()
field_3 = serializers.CharField()
obj_list = [{"field_1": ["value_11a", "value_11b",],
"field_2": "value_12",
"field_3": "value_13",
},
{"field_1": ["value_21a", "value_21b",],
"field_2": "value_22",
"field_3": "value_23",
},
]
serializer = MySimpleSerializer(obj_list, many=True)
|
|
-
|
-
|
data =
{'f1':'value1', 'f2':'value2'}
content = MyRenderer().render(data) |
|
- Routers (and ViewSets)
-
generated
by router.register
|
|
provided
by ViewSet
|
URL style
|
URL name |
HTTP method
|
action
|
affected by:
|
{prefix}/
|
{basename}-list |
GET
|
list
|
- queryset
- [def get_queryset]
- def get_queryset(self):
# only entries that
belong to user
user =
self.request.user
# (this model has
user as a foreignkey)
return
user.thisobject_set.all()
- def get_queryset(self):
# only entries that
belong to user
u = self.request.user
# (this model has
user as a foreignkey)
return
Thismodel.objects.filter(user=u)
|
POST
|
create
|
- [def pre_save]
- def pre_save(self,
obj):
# toto_id is passed
using the serializer, and is used to
select
# an otherobject,
referenced as a ForeignKey
try:
otherobject =
Othermodel.objects.get(toto_id=self.request.DATA['toto_id'])
except
Othermodel.DoesNotExist:
raise Http404
obj.otherobject =
otherobject
- [def create]
- # drf 2.x
def create(self,
request, *args, **kwargs):
serializer =
self.get_serializer(data=request.DATA,
files=request.FILES)
if
serializer.is_valid():
self.pre_save(serializer.object)
#set_password
serializer.object.set_password(serializer.data['password'])
self.object =
serializer.save(force_insert=True)
self.post_save(self.object,
created=True)
return Response(serializer.data,
status=status.HTTP_201_CREATED)
return
Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
- # drf 3.x
def perform_create(self,
serializer):
user =
serializer.save()
# set the password
user.set_password(serializer.validated_data['password'])
user.save()
|
{prefix}/{lookup}/
|
{basename}-detail |
GET
|
retrieve
|
- queryset
- [def get_queryset]
- [lookup_field]
- [def get_object]
- def get_object(self):
# as extendeduser pk
is not explicitly provided,
# get it from the
user on the request
p = self.request.user
try:
return ExtendedUser.objects.get(pk=p.id)
except
ExtendedUser.DoesNotExist:
raise Http404
- [def pre_save]
- def pre_save(self,
obj):
# as extendeduser pk
is not explicitly provided,
# get it from the
user on the request
obj.extendeduser =
self.request.user
|
PUT
|
update
|
PATCH
|
partial_update
|
DELETE
|
destroy
|
{prefix}/{lookup}/{methodname}/ |
{basename}-{method-name} |
GET
|
@action() (new
in drf 3.9)
@detail_route()
(deprecated)
@list_route() (deprecated)
def method_name(...)
|
POST
|
@action() (new
in drf 3.9)
@detail_route(methods=['post'])
@list_route(methods=['post'])
def method_name(...) |
- Resum de
Routers i Viewsets / Summary:
|
my_project/urls.py
|
|
|
|
urlpatterns =
patterns('',
url(r'^v1/api/',
include('my_app_1.urls', namespace='v1')),
url(r'^v1/api/',
include('my_app_2.urls', namespace='v1')),
)
|
|
|
|
my_app_1/url.py
|
api_root
(views.py)
|
views.py
|
ViewSet (and derived) with
list,
+ Routers
|
from
rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'totos', views.TotoViewSet,
base_name='toto')
urlpatterns = patterns('',
url(r'^',
include(router.urls)),
)
|
- from rest_framework import viewsets
class
TotoViewSet(viewsets.ReadOnlyModelViewSet):
...
- class TotoViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows
a toto to be viewed or edited.
"""
queryset =
MyModel.objects.all()
serializer_class =
MyModelSerializer
|
from
rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register(r'totos', views.TotoViewSet,
base_name='toto')
urlpatterns = patterns('',
url(r'^',
include(router.urls)),
) |
from
rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
@api_view(('GET','HEAD'))
def api_root(request, format=None):
return Response({
'totos': reverse('toto-list', request=request,
format=format),
})
|
ViewSet
(and derived) without list,
+ Routers |
router = SimpleRouter()
router.register(r'new_toto',
views.TotoCreateViewSet, base_name='newtoto')
urlpatterns = patterns('',
url(r'^',
include(router.urls)),
) |
@api_view(('GET','HEAD'))
def api_root(request, format=None):
return Response({
'new_toto': reverse('newtoto-list',
request=request, format=format),
}) |
- class
CreateViewSet(mixins.CreateModelMixin,
viewsets.GenericViewSet):
pass
class
TotoCreateViewSet(viewsets.CreateViewSet):
queryset =
Toto.objects.all()
serializer_class =
TotoSerializer
|
APIView (and
derived)
|
from
django.conf.urls import include
urlpatterns = patterns('',
url(r'^update_usuari/$',
views.UsuariUpdateAPIView.as_view(),
name='updateusuari-detail' ),
url(r'^$', 'api_root'),
)
# Login and logout views for the browsable API
urlpatterns += patterns('',
url(r'^api-auth/',
include('rest_framework.urls',
namespace='rest_framework')),
)
|
@api_view(('GET','HEAD'))
def api_root(request, format=None):
return Response({
'update_usuari': reverse('updateusuari-detail',
request=request, format=format),
}) |
- from rest_framework.renderers import
JSONRenderer, XMLRenderer,
BrowsableAPIRenderer
class
UsuariUpdateAPIView(generics.UpdateAPIView):
...
renderer_classes =
(BrowsableAPIRenderer, JSONRenderer,)
...
# as participant pk is
not explicitly provided,
# get it from the user on
the request
def get_object(self):
u
= self.request.user
try:
return
Participant.objects.get(pk=u.id)
except Participant.DoesNotExist:
raise
Http404
|
- router automatically creates, from a ViewSet:
- api_root automatically generated by DefaultRouter (but
not by SimpleRouter)
- registre / registration
(urls.py):
from django.conf.urls
import patterns
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register(r'prefix',
viewset,
base_name='basename')
urlpatterns = patterns('',
url(r'^',
include(router.urls)),
)
- alternative to router (use *APIView instead of
ViewSet):
urlpatterns = patterns('',
url(r'^prefix/$',
views.ExtendedUserUpdateAPIView.as_view(),
name='basename-detail'
),
)
- Routers aniuats /
Nested routers
- drf-nested-routers
- Installation
pip install drf-nested-routers
- Usage:
- views.py
class
NameserverViewSet(viewsets.ViewSet):
def
get_queryset(self):
domain_pk =
self.kwargs.get('domain_pk', None)
if domain_pk:
nameservers
=
Nameserver.objects.filter(domain=domain_pk)
else:
nameervers
= Nameserver.objects.all()
return nameservers
-
|
my_app/models.py
|
my_app/urls.py
|
my_app/views.py
|
my_app/serializers.py
|
ManyToMany
|
class
Primary(models.Model):
field_1 = ...
class Secondary(models.Model):
primary = models.ForeignKey(
PrimaryModel,
related_name='secondaries')
field_a = ...
|
from rest_framework_nested
import routers
from my_app import views
primary_router = routers.SimpleRouter()
primary_router.register(r'primaries',
views.PrimaryViewSet)
secondary_router =
routers.NestedSimpleRouter(primary_router,
r'primaries', lookup='primary')
secondary_router.register(r'users',
views.SecondaryViewSet)
urlpatterns = patterns('',
url(r'^',
include(primary_router.urls)),
url(r'^',
include(secondary_router.urls)),
)
|
from
rest_framework import viewsets
class
PrimaryViewSet(viewsets.ModelViewSet):
queryset =
Primary.objects.all()
serializer_class =
PrimarySerializer
class
SecondaryViewSet(viewsets.ModelViewSet):
queryset =
Secondary.objects.all()
serializer_class =
SecondarySerializer
def
get_queryset(self):
# only entries that belong to specified
parent
specified_primary_pk = int(
self.kwargs.get('primary_pk', None) )
if specified_primary_pk:
secondaries
=
Poster.objects.filter(primary_id=specified_primary_pk)
else:
secondaries
= Poster.objects.all()
return secondaries
def
perform_create(self, serializer):
# get the parent from the lookup field
primary_pk
specified_primary_pk = int(
self.kwargs.get('primary_pk', None) )
serializer.save(primary_id=specified_primary_pk)
|
class
PrimarySerializer(serializers.ModelSerializer):
class Meta:
model = Primary
class
SecondarySerializer(serializers.ModelSerializer):
class Meta:
model = Secondary
|
OneToOne
|
class
PrimaryModel(models.Model):
field_1 = ...
class SecondaryModel(models.Model):
primary = models.OneToOneField(
PrimaryModel, related_name='secondary')
field_a = ...
|
primary_router
= routers.SimpleRouter()
primary_router.register(r'primaries',
views.PrimaryViewSet)
urlpatterns = patterns('',
url(r'^',
include(primary_router.urls)),
url(r'^primaries/(?P<primary_id>[^/]+)/secondary/$',
views.SecondaryView.as_view(),
name='secondary-detail')
)
|
from
rest_framework import mixins
from rest_framework import generics
class
PrimaryViewSet(viewsets.ModelViewSet):
queryset =
Primary.objects.all()
serializer_class =
PrimarySerializer
class
CreateRetrieveUpdateDestroyAPIView(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
"""
Concrete view for
creating, retrieving, updating or
deleting a model instance.
"""
def post(self,
request, *args, **kwargs):
return self.create(request, *args,
**kwargs)
def get(self,
request, *args, **kwargs):
return self.retrieve(request, *args,
**kwargs)
def put(self,
request, *args, **kwargs):
return self.update(request, *args,
**kwargs)
def patch(self,
request, *args, **kwargs):
return self.partial_update(request,
*args, **kwargs)
def delete(self,
request, *args, **kwargs):
return self.destroy(request, *args,
**kwargs)
class
SecondaryView(CreateRetrieveUpdateDestroyAPIView):
queryset =
Secondary.objects.all()
serializer_class =
SecondarySerializer
lookup_field =
'primary_id'
def
perform_create(self, serializer):
# get the parent from the lookup field
primary_id (automatically created)
specified_primary_id = int(
self.kwargs.get('primary_id',
None) )
serializer.save(primary_id=specified_primary_id)
|
- ...
- Views
- Parsers
- Tutorial
1:
Serialization
- Exemples / Examples:
- XMLParser
- HierarchicalParser
- XLSParser
- Simple parser:
from
rest_framework.compat import BytesIO
from
rest_framework.parsers
import
JSONParser
stream = BytesIO(content)
data = JSONParser().parse(stream)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
...
- MultiPartJSONParser
- Multipart parser,
adapted to work with PHP
- my_project/settings.py
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'my_app.parsers.PhpMultiPartParser',
),
)
- my_app/parsers.py
import
re
from rest_framework import parsers
from django.http.request import QueryDict
from django.utils.datastructures import
MultiValueDict
def compact_dataandfiles(dataandfiles):
"""
Convert a DataAndFiles
structure of type:
data:
{"field1[0]":"value1",
"field1[0]":"value2"}
to:
data:
{"field1":["value1","value2"] }
"""
compact_data_qd =
QueryDict('', mutable=True)
regex =
re.compile(r"\[.*\]")
tmp_mdict =
MultiValueDict()
#for key, valuelist in
dataandfiles.data._iterlists():
for key,
valuelist in dataandfiles.data.lists():
# remove "[...]" from key
compact_key = regex.sub(r"", key)
if compact_key in compact_data_qd:
tmp_mdict.setlist(compact_key,
valuelist)
compact_data_qd.update(tmp_mdict)
else:
compact_data_qd.setlist(compact_key,
valuelist)
# build a new
DataAndFiles with the same files and
compact data
compact_dataandfiles =
parsers.DataAndFiles(compact_data_qd,
dataandfiles.files)
return
compact_dataandfiles
class PhpMultiPartParser(parsers.MultiPartParser):
"""
MultiPartParser that
accepts requests with data of syntax:
"field1[0]":"value1",
"field1[0]":"value2", e.g. from PHP
"""
def parse(self, stream,
media_type=None, parser_context=None):
dataAndFiles =
parsers.MultiPartParser.parse(self,
stream, media_type=media_type,
parser_context=parser_context)
dataAndFiles =
compact_dataandfiles(dataAndFiles)
return dataAndFiles
- Parse content from uploaded
file (from additional
button in admin)
- Parse content from uploaded
file (with curl):
class
TotoViewSet(viewsets.ModelViewSet):
queryset =
TotoModel.objects.all()
serializer_class =
TotoSerializer
permission_classes =
(permissions.IsAuthenticated,)
renderer_classes =
(BrowsableAPIRenderer, JSONRenderer,
CSVRenderer)
parser_classes =
(JSONParser, MultiPartParser,)
def create(self, request,
*args, **kwargs):
data = None
#
data is sent as direct json (literally or from
file: @) (JSONParser):
#
curl -X POST -H
"Content-Type:application/json" -u admin:admin
http://127.0.0.1:8000/totos/ -d '{"a":"b"}'
# curl -X POST -H
"Content-Type:application/json" -u admin:admin
http://127.0.0.1:8000/totos/ -d @toto.json
if
request._content_type == 'application/json':
data = request.DATA
#
data is sent inside a file that is uploaded ( MultiPartParser ):
#
curl -X POST -H
"Content-Type:multipart/form-data" -u
admin:admin http://127.0.0.1:8000/totos/ -F
"file=@toto.json;type=application/json"
#
note: if type is not specified, it defaults to
"application/octet-stream"
else:
print
request.FILES['file']
fitxer
= File(request.FILES['file'])
content
= fitxer.read()
stream
= BytesIO(content)
if request.FILES['file'].content_type ==
'application/json':
data
= JSONParser().parse(stream)
else:
print
"No parser for content type: %s" %
(request.FILES['file'].content_type)
errors
= dict()
errors['error']
= "No parser for content type: %s" %
(request.FILES['file'].content_type)
return
Response(errors,status=status.HTTP_400_BAD_REQUEST)
fitxer.close()
#
serialize the data:
serializer = self.get_serializer(data=data,
files=request.FILES, many=True)
#
if all is fine, save the object in the
database:
if
serializer.is_valid():
self.pre_save(serializer.object)
self.object
= serializer.save(force_insert=True)
self.post_save(self.object,
created=True)
return
Response(serializer.data,
status=status.HTTP_201_CREATED)
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
- Serialització /
Serialization
- Serializers
depth
- Dealing
with
multiple objects
- Dynamically
modifying
fields
class
ParticipantSerializer(serializers.ModelSerializer):
def __init__(self, *args,
**kwargs):
...
#
Don't pass the 'fields' arg up to the
superclass
fields =
kwargs.pop('fields', None )
...
class
ParticipantSerializer(serializers.ModelSerializer):
gcmdevice_registration_id
= serializers.SlugRelatedField(many=True,
slug_field='registration_id',
required=False)
apnsdevice_registration_id =
serializers.SlugRelatedField(many=True,
slug_field='registration_id',
required=False)
def __init__(self, *args,
**kwargs):
dades = kwargs.get('data')
#
si s'ha fet un post
if dades:
if 'gri' in dades:
#
Don't pass the 'gri' arg up to the
superclass
gri
= dades.pop('gri', None)
if
gri:
#
crea una llista amb un sol element
llista
= [gri]
#
posa aquesta llista com si s'hagués passat
per paràmetre
kwargs.get('data')['gcmdevice_registration_id']
= llista
#
Instantiate the superclass normally
super(ParticipantSerializer,
self).__init__(*args, **kwargs)
class Meta:
model = Participant
fields =
('id','username','password','first_name','last_name',
'email', 'nif', 'phone',
'apnsdevice_registration_id',
'gcmdevice_registration_id')
- Fields
- Boolean
- Default value
- Model
defaults
ignored on empty text field or empty
boolean field. #1101
- Solution
- models.py
class
MyModel(models.Model):
is_something =
models.BooleanField(default=True)
- serializers.py
class
MyModelSerializer(serializers.ModelSerializer):
# need to
specify default=True because
default value from model is not
taken
is_something =
serializers.BooleanField(default=True)
- Basic example
- serializers.py
from rest_framework
import serializers
from my_app.models import MyModel
class
MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModes
fields = ('field1','field2',)
- No
serialització de camps buits / Do not serialize empty
fields
- serializers.py
from rest_framework
import serializers
from rest_framework.fields import SkipField
class
NonNullSerializer(serializers.ModelSerializer):
"""
Do not serialize empty
fields.
"""
def to_representation(self,
instance):
"""
Object instance -> Dict of primitive
datatypes.
http://stackoverflow.com/questions/27015931/remove-null-fields-from-django-rest-framework-response
"""
ret
= OrderedDict()
fields = [field for field in
self.fields.values() if not field.write_only]
for
field in fields:
try:
attribute
= field.get_attribute(instance)
except SkipField:
continue
if (attribute is not None) and not
(isinstance(attribute, (basestring)) and
len(attribute)==0):
represenation
= field.to_representation(attribute)
if represenation is None:
#
Do not seralize empty objects
continue
if
isinstance(represenation, list) and not
represenation:
#
Do not serialize empty lists
continue
ret[field.field_name] = represenation
return ret
class MyModelSerializer(NonNullSerializer):
...
- Serialització
dels queryparams / Queryparams serialization
- Use
a serializer for the query parameters
- Good
Practice to Handle Incoming Data in DRF
- Exemples / Examples
- ...
class MyQueryparamsSerializer (serializers.Serializer):
first_field =
serializers.CharField(default=
first_field_default_value )
second_field =
serializers.IntegerField(required=False)
class MyViewSet(viewsets.ModelViewSet)
@swagger_auto_schema(
method="get", query_serializer=MyQueryparamsSerializer
)
@action(
methods=["get"]
)
def my_method(self, request, *args, **kwargs):
#
get queryparams using a serializer
queryparams_serializer =
MyQueryparamsSerializer(data=request.query_params)
if not
queryparams_serializer.is_valid():
return Response(queryparams_serializer.errors,
status.HTTP_400_BAD_REQUEST)
first_field_value =
queryparams_serializer.validated_data("first_field")
second_field_value =
queryparams_serializer.validated_data("second_field",
second_field_default_value)
- ...
- Serialitzadors
diferents
a l'entrada (request) i a la sortida (response) /
Different serializers for input (request) and output
(response)
- drf-yasg
- get_serializer_class(self)
- views.py
class
MyModelViewSet(viewsets.ModelViewSet):
serializer_class =
MyModelSerializer
# used by overriden
get_serializer_class
input_serializer_class
= MyModelInputSerializer
def get_serializer_class(self):
if self.request.method == 'GET':
return
self.serializer_class
else:
return
self.input_serializer_class
- serializers.py
class
MyModelInputSerializer(serializers.ModelSerializer):
...
class
MyModelSerializer(serializers.ModelSerializer):
...
- Python rest framwork:
different serializers for input and output of
service?
read_only=True
write_only=True
- get_response()
- serializer_class specifies the request
serializer and we explicitely define the
response by calling the response serializer
- views.py
(from django_rest_auth)
class
LoginView(GenericAPIView):
"""
Check the credentials
and return the REST Token
if the credentials are
valid and authenticated.
Calls Django Auth login
method to register User ID
in Django session
framework
Accept the following
POST parameters: username, password
Return the REST
Framework Token Object's key.
"""
permission_classes =
(AllowAny,)
serializer_class =
LoginSerializer
token_model = Token
response_serializer
= TokenSerializer
def login(self):
self.user =
self.serializer.validated_data['user']
self.token, created =
self.token_model.objects.get_or_create(
user=self.user)
if getattr(settings, 'REST_SESSION_LOGIN',
True):
login(self.request, self.user)
def get_response(self):
return Response(
self.response_serializer(self.token).data,
status=status.HTTP_200_OK
)
def post(self, request,
*args, **kwargs):
self.serializer =
self.get_serializer(data=self.request.data)
self.serializer.is_valid(raise_exception=True)
self.login()
return self.get_response()
- @detail_route (use @action
instead)
- serializer_class specifies the response
serializer. Request serializer is not needed: we
directly use request.data.get(...)
- my_app/serializers.py
class
MyResponseSerializer(serializers.Serializer):
output_field_1
= serializers.CharField()
- my_app/views.py
from
my_app.serializers import MyResponseSerializer
Class
MyModelViewSet(viewsets.ModelViewSet):
...
@detail_route(methods=['post'],
serializer_class =
MyResponseSerializer,
permission_classes =
(permissions.IsAuthenticated,))
def my_function(self,
request, *args, **kwargs):
"""
My description.
---
response_serializer: MyResponseSerializer
parameters_strategy:
form: replace
omit_parameters:
- query
"""
input_field_1 =
request.data.get('input_field_1','my_default')
# build temporary object
obj = dict()
obj['output_field_1'] = ...
# serialize it
serializer = self.get_serializer(obj)
return Response(serializer.data)
- Canvi de nom
del camp / Change name of the field
- solves problem:
'...' object has no
attribute 'new_ field_name'
new_field_name =
serializers.IntegerField(source='existing_field_name')
new_field_name =
serializers.ModelField(model_field=MyModel()._meta.get_field('existing_field_name'))
new_field_name =
OtherSerializer(source='existing_field_name')
new_field_name =
serializers.SlugRelatedField(slug_field=' existing_field_name ',source='fk_field',queryset=Parent.objects.all())
- Paraules
reservades com a camps / Reserved words as fields
- Serializers
and choices
- Note: serializers.ChoiceField does not use
display_name for json
(serialization/deserialization); only for forms
- If you want to serialize the associated text in
choices, instead of the value, see bellow:
- models.py
class
MyModel(models.Model):
MY_FIELD_CHOICES = (
(0,"zero"),
(1,"one"),
(2,"two"),
)
my_field =
models.IntegerField(choices=MY_FIELD_CHOICES)
- Option 1: only works for serialization
- serializers.py
class
MyModelSerializer(serializers.ModelSerializer):
my_field =
serializers.CharField(source='get_my_field_display')
class Meta:
model = MyModel
fields = ('my_field',)
- Option 2: works for serialization and
deserialization
- serializers.py
from
rest_framework import serializers
class
EnhancedChoiceField(serializers.ChoiceField):
"""
Serializer for choice
field.
"""
# serialization
def
to_representation(self, obj):
return self.choices[obj]
# deserialization
# NOTE: does not work
with i18n values
def
to_internal_value(self, data):
if data.isdigit():
return
int(data)
else:
for key,val in self.choices.iteritems():
if
val==data:
return
key
raise serializers.ValidationError("Not a
valid choice. Valid choices are:
{}".format(self.choices.values()) )
class MyModelSerializer(serializers.ModelSerializer):
my_field =
EnhancedChoiceField(choices=MyModel. MY_FIELD_CHOICES,
required=False)
class Meta:
model = MyModel
fields = ('my_field',)
- views.py (needed by drf-swagger)
(type: replaces serializer; parameters: replaces
form)
class
MyModelView(...):
"""
View for my_model
---
# type: replaces the
serializer ("Response Class") (all fields
must be specified)
# parameters:
overwrites the form ("Parameters") (only
changed fields need to be specified)
xxx:
type:
my_field:
description: "Some
description here"
required: true
type: choice
enum:
- zero
- one
- two
parameters:
- name: my_field
description: "Some description here"
required: true
paramType: form
type: choice
enum:
- zero
- one
- two
"""
...
- Create and
perform_create, update and perform_update (see also Views)
- CreateModelMixin.create
- self.perform_create
- MyModelViewSet.perform_create
- serializer.save
- BaseSerializer.save
- self.create
- MyModelSerializer.create
- super
- TaggitSerializer.create
- super
- ModelSerializer.create
- ModelClass._default_manager.create(**validated_data)
- obj.save
- MyModel.save
- MyModel.post_save
- TaggitSerializer._save_tags
-
rest_framework/mixins.py |
rest_framework/serializers.py |
rest_framework/serializers.py
|
my_app/views.py
|
|
my_app/serializers.py
|
class
CreateModelMixin(object):
class MyModelViewSet(viewsets.ModelViewSet):
# usually not overwritten
def create(self,
request, *args, **kwargs):
serializer =
self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers =
self.get_success_headers(serializer.data)
return Response(serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
# function to be
overwritten in my_app/views.py
def perform_create(self,
serializer):
#
e.g. get parent object from pk specified in
url
...
#
explicit call to save is the bare minimum
serializer.save()
|
|
|
|
class
BaseSerializer(Field):
...
def save(self,
**kwargs):
...
else:
self.instance
= self.create(validated_data)
|
|
|
|
class
ModelSerializer(Serializer):
class MyModelSerializer(TaggitSerializer,
serializers.ModelSerializer):
# overwritten e.g. to deal
with two serializations at the same time
(FK)
def create(self,
validated_data):
#
e.g. pop from validated_data, to create
related objects
...
# essentially:
return self.Meta.model.objects.create(**validated_data)
class Meta:
model = MyModel
|
|
|
class
MyModelSerializer(serializers.ModelSerializer):
def create(self,
validated_data):
#
if instance already exists, return it
instance, created = self.Meta.model.objects.get_or_create(**validated_data)
return instance
class Meta:
model = MyModel
|
class RetrieveModelMixin(object):
def retrieve(self,
request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data) |
|
|
class ListModelMixin(object):
def list(self,
request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page,
many=True)
return
self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset,
many=True)
return Response(serializer.data) |
|
|
class
UpdateModelMixin(object):
class MyModelViewSet(viewsets.ModelViewSet):
"""
Update a model instance.
"""
# usually not overwritten
def update(self,
request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance,
data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
# function to be
overwritten in my_app/views.py
def perform_update(self,
serializer):
#
explicit call to save is the bare minimum
serializer.save()
def partial_update(self,
request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
|
|
|
|
class
BaseSerializer(Field):
...
def save(self,
**kwargs):
...
if self.instance is not None:
self.instance
= self.update(self.instance,
validated_data)
|
|
|
|
class
ModelSerializer(Serializer):
class
MyModelSerializer(serializers.ModelSerializer):
# overwritten e.g. to deal
with two serializations at the same time
(FK)
def update(self,
instance,
validated_data):
... |
|
|
class
ModelSerializer(Serializer):
class
TaggitSerializer(serializers.Serializer):
class MyModelSerializer(TaggitSerializer,
serializers.ModelSerializer):
# overwritten e.g. to deal
with two serializations at the same time
(FK)
def update(self,
instance,
validated_data):
...
#
update mymodel own fields and tags
#
no need to call
serializers.ModelSerializer.update because
it is called from TaggitSerializer.update
updated_instance =
TaggitSerializer.update(self, instance,
validated_data)
...
return updated_instance
|
from
django.shortcuts import get_object_or_404
from django.http import Http404
class MyModelViewSet(viewsets.ModelViewSet):
"""
Create (or update) a
model instance.
"""
def create(self,
request, *args, **kwargs):
#
check if the instance already exists
instance = None
#
get parameter from data
#name = request.data.get('name',None)
try:
queryset = self.get_queryset()
if name:
filter_kwargs = dict()
#filter_kwargs['name'] = name
instance
= get_object_or_404(queryset,
**filter_kwargs)
except Http404:
pass
if instance:
# update it
#self.kwargs['name']
= name # set parameter to kwargs (url in
nested viewsets)
return self.update(request, *args, **kwargs)
else:
# create a new instance
serializer =
self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer,
remove_overlapped=remove_overlapped)
headers =
self.get_success_headers(serializer.data)
return
Response(serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
|
|
|
class
MyModelViewSet(viewsets.ModelViewSet):
"""
Update (or create) a
model instance.
"""
def update(self,
request, *args,
**kwargs):
partial = kwargs.pop('partial', False)
#
check if the instance already exists
instance = None
#
get parameter from kwargs (url in nested
viewsets)
#name = kwargs.get('name', None)
try:
instance = self.get_object()
except Http404:
pass
if instance:
# update it
serializer
= self.get_serializer(instance,
data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer,
remove_overlapped=remove_overlapped)
return Response(serializer.data)
else:
# create
it
#request.data['name'] = name # set parameter
to data
return self.create(request, *args, **kwargs)
|
|
|
- Serializers
relations
-
camp / field
|
mostra /
shows
|
read
(serialization)
|
write
(deserialization)
|
args
|
info
|
comments
|
- 2.x
Field(source='function_in_model_class')
3.x
- fields( ...,
'function_in_model_class')
|
output from
function defined in models.Model |
x
|
-
|
|
|
|
Field(source='fk_field_name.field')
|
field from fk
|
x
|
-
|
|
direct FK
(class where FK is declared)
|
|
WritableField
|
|
.to_native(self,
obj) |
.from_native(self,
data) |
|
you have to
implement .from_native(self,value),
.to_native(self,value)
|
|
ModelField
|
|
|
|
|
|
|
RelatedField
|
__unicode__
|
x
|
-
|
|
|
|
CustomRelatedField
|
|
.to_native(self,
obj)
.to_representation(self, obj)
|
.from_native(self,
data)
.to_internal_value(self, data)
|
|
|
|
SerializerMethodField('function_in_serializer')
def function_in_serializer(self,obj)
qs=...objects.filter(...)
serializer=...Serializer(qs)
return serializer.data |
output from
function defined in
serializers.ModelSerializer
(aniuat
i filtrat / nested and filtered) |
x
|
-
|
|
|
|
PrimaryKeyRelatedField
|
pk
|
x
|
x
|
|
reverse FK
(some class points to here using a fk)
|
write: can be
used to attach a child to its parent via a
FK
|
HyperlinkedRelatedField
|
url_to_items
|
|
|
|
reverse FK |
|
tracks
= SlugRelatedField (slug_field='title')
|
slug_field
|
x
|
x
|
slug_field
|
direct FK,
reverse FK |
- write: can be used to attach a child
to its parent via a slug_field
- in models.py, slug_field has to be
unique=True
|
HyperlinkedIdentityField
|
url_to_set
|
|
|
|
|
|
OtherSerializer
(many=True )
|
(nested)
|
x
|
x
|
|
|
- write: can be used to recursively add
childs
|
- Writable
foreign/one2one keys (library_site)
-
|
library_site/urls.py
|
|
|
|
from
django.contrib import admin
from django.urls import include, path
urlpatterns = [
# app: publications
path("publications/",
include("publications.urls")),
#
djangorestframework auth
# drf-yasg swagger
documentation
# admin
path("admin/",
admin.site.urls),
]
old:
urlpatterns = patterns('',
url(r'^admin/',
include(admin.site.urls)),
# djangorestframework
auth
url(r'^api-auth/',
include('rest_framework.urls',
namespace='rest_framework')),
# swagger
documentation
url(r'^docs/',
include('rest_framework_swagger.urls')),
url(r'^v1/api/',
include('publications.urls',
namespace='v1')),
#url(r'^v1/api/',
include('my_app_2.urls',
namespace='v1')),
#url(r'^v1/api/',
include('my_app_3.urls',
namespace='v1')),
)
|
|
|
publications/models.py
|
publications/urls.py
|
publications/views.py
|
publications/serializers.py
|
from
django.db import models
class ISBN(models.Model):
"""
ISBN code
"""
number =
models.IntegerField()
class Publisher(models.Model):
"""
Publisher
"""
name =
models.CharField(max_length=100)
address =
models.TextField(blank=True,
null=True)
class Author(models.Model):
"""
Author
"""
first_name =
models.CharField(max_length=100)
last_name =
models.CharField(max_length=100)
class Book(models.Model):
"""
Book
"""
isbn = models.OneToOneField(
ISBN,
blank=True,
null=True,
# when related ISBN is deleted, also
delete this book
on_delete=models.CASCADE,
)
publisher = models.ForeignKey(
Publisher,
related_name="publications",
blank=True,
null=True,
# when related publisher is deleted,
also delete this book
on_delete=models.CASCADE,
)
authors = models.ManyToManyField(Author,
related_name="publications")
title =
models.CharField(max_length=200)
class Comment(models.Model):
book = models.ForeignKey(
Book,
related_name="comments",
# when related book is deleted, also
delete this comment
on_delete=models.CASCADE,
)
text =
models.TextField()
|
from
django.urls import include, path
from rest_framework import routers
from . import views
app_name = "publications"
book_router = routers.DefaultRouter()
book_router.register(r"books",
views.BookViewSet)
urlpatterns = [
path("",
include(book_router.urls)),
]
old:
from django.conf.urls import patterns,
include, url
#from rest_framework import routers
from rest_framework_nested import
routers
from library import views
book_router = routers.SimpleRouter()
book_router.register(r'books',
views.BookViewSet)
comment_router =
routers.NestedSimpleRouter(book_router,
r'books', lookup='book')
comment_router.register(r'comments',
views.CommentViewSet)
urlpatterns = patterns('',
url(r'^',
include(book_router.urls)),
url(r'^',
include(comment_router.urls)),
)
|
from
django.shortcuts import render
from django.shortcuts import
get_object_or_404
from rest_framework import viewsets
from rest_framework import status
from rest_framework.response import
Response
from rest_framework.exceptions import APIException
from .models import Book, Comment
from .serializers import BookSerializer,
CommentSerializer
class
BookViewSet(viewsets.ModelViewSet):
queryset =
Book.objects.all()
serializer_class =
BookSerializer
class
CommentViewSet(viewsets.ModelViewSet):
queryset =
Comment.objects.all()
serializer_class =
CommentSerializer
def get_queryset(self):
# only entries that belong to specified
parent
book_pk = self.kwargs.get('book_pk',
None)
book = get_object_or_404(Book,
pk=book_pk)
comment =
Comment.objects.filter(book=book)
return comment
def perform_create(self,
serializer):
# deal with exceptions
try:
# try something
except Exception as e:
# raise Http404
raise
APIException("Error:
%s" % e)
# get the parent from the lookup field
book_pk
book_pk = self.kwargs.get('book_pk',
None)
book = get_object_or_404(Book,
pk=book_pk)
serializer.save(book=book)
|
from
rest_framework import serializers
from .models import Book, ISBN,
Publisher, Comment, Author
class
ISBNSerializer(serializers.ModelSerializer):
class Meta:
model = ISBN
fields = ('number',)
class
PublisherSerializer(serializers.ModelSerializer):
class Meta:
model = Publisher
fields = ('name','address',)
class
CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ('text',)
class
AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('first_name','last_name',)
class
BookSerializer(serializers.ModelSerializer):
isbn =
ISBNSerializer()
publisher =
PublisherSerializer()
authors =
AuthorSerializer( many=True )
comments =
CommentSerializer( many=True,
read_only=True )
def create(self,
validated_data):
if 'isbn' in validated_data:
# create the ISBN object
isbn_data
= validated_data.pop('isbn')
isbn = ISBN.objects.create(**isbn_data)
# relate it to the book
validated_data['isbn']
= isbn
if 'publisher' in validated_data:
publisher_data
= validated_data.pop('publisher')
publisher,
publisher_created =
Publisher.objects.get_or_create(**publisher_data)
validated_data['publisher']
= publisher
authors_list = None
if 'authors' in validated_data:
authors_data_list
= validated_data.pop('authors')
authors_list
= []
for author_data in authors_data_list:
author,
author_created =
Author.objects.get_or_create(**author_data)
authors_list.append(author)
book =
Book.objects.create(**validated_data)
if authors_list:
book.authors.add(*authors_list)
return book
def update(self,
instance,
validated_data):
if 'isbn' in validated_data:
# extract information about the ISBN
object
isbn_data
= validated_data.pop('isbn')
# get the isbn object related to this
book
isbn = instance.isbn
# update it
for attr, value in isbn_data.items():
setattr(isbn,
attr, value)
isbn.save()
if 'publisher' in validated_data:
# extract information about the
publisher object
publisher_data
= validated_data.pop('publisher')
# get the publisher object related to
this book
publisher
= instance.publisher
# update it
for attr, value in
publisher_data.items():
setattr(publisher,
attr, value)
publisher.save()
authors_list = None
if 'authors' in validated_data:
authors_data_list
= validated_data.pop('authors')
authors_list
= []
for author_data in authors_data_list:
#
TODO: other than add
#
Best solution: nested router?
#
TODO: if get returns more than one
object
author,
author_created =
Author.objects.get_or_create(**author_data)
authors_list.append(author)
# update book own fields
updated_instance =
serializers.ModelSerializer.update(self,
instance, validated_data)
# add the authors (m2m)
if authors_list:
updated_instance.authors.add(*authors_list)
return updated_instance
class Meta:
model = Book
fields =
("title","isbn","publisher","authors","comments",)
|
|
from
django.conf.urls import patterns,
include, url
from rest_framework import routers
from library import views
book_router = routers.SimpleRouter()
book_router.register(r'books',
views.BookViewSet)
urlpatterns = patterns('',
url(r'^',
include(book_router.urls)),
url(r'^books/(?P<pk>[^/]+)/comments/$',
views.BookCommentsCreateRetrieveView.as_view(),
name='bookcomments-detail'),
) |
from
django.shortcuts import render
from django.shortcuts import
get_object_or_404
from rest_framework import viewsets
from rest_framework import status
from rest_framework.response import
Response
from models import Book, Comment
from serializers import BookSerializer,
CommentSerializer
class
BookViewSet(viewsets.ModelViewSet):
queryset =
Book.objects.all()
serializer_class =
BookSerializer
class
CreateRetrieveAPIView(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
generics.GenericAPIView):
"""
Concrete view for
creating or retrieving a model instance.
"""
def post(self,
request, *args, **kwargs):
return self.create(request, *args,
**kwargs)
def get(self,
request, *args, **kwargs):
return self.retrieve(request, *args,
**kwargs)
class
BookCommentsCreateRetrieveView(CreateRetrieveAPIView):
queryset =
Comment.objects.all()
serializer_class =
CommentSerializer
def get_queryset(self):
# only entries that belong to specified
parent
book_pk = self.kwargs.get('book_pk',
None)
book = get_object_or_404(Book,
pk=book_pk)
comment =
Comment.objects.filter(book=book)
return comment
def perform_create(self,
serializer):
# get the parent from the lookup field
book_pk
book_pk = self.kwargs.get('book_pk',
None)
book = get_object_or_404(Book,
pk=book_pk)
serializer.save(book=book)
|
|
-
|
|
my_project/urls.py |
|
|
|
|
from
django.conf.urls import patterns, include,
url
from django.contrib import admin
urlpatterns = patterns('',
# admin
url(r'^admin/',
include(admin.site.urls)),
# djangorestframework
auth
url(r'^api-auth/',
include('rest_framework.urls',
namespace='rest_framework')),
# swagger documentation
url(r'^docs/',
include('rest_framework_swagger.urls')),
url(r'^v1/api/',
include('my_app.urls', namespace='v1')),
#url(r'^v1/api/',
include('my_app_2.urls', namespace='v1')),
#url(r'^v1/api/',
include('my_app_3.urls', namespace='v1')),
)
|
|
|
|
my_app/models.py
|
my_app/urls.py
|
my_app/views.py
|
serializers.py
|
|
from
django.db import models |
from my_app
import views |
from
rest_framework import viewsets
|
from
rest_framework import serializers
|
ForeignKey
|
class
ModelA(models.Model):
field_a_1 =
models.CharField( max_length=100 )
class ModelB(models.Model):
model_a = models.ForeignKey(
ModelA, related_name='models_b',
blank=True, null=True )
field_b_1 =
models.CharField( max_length=100 )
|
from
rest_framework import routers
model_a_router = routers.SimpleRouter()
model_a_router.register(r'models_a',
views.ModelAViewSet)
model_b_router = routers.SimpleRouter()
model_b_router.register(r'models_b',
views.ModelBViewSet)
urlpatterns = patterns('',
url(r'^',
include(model_a_router.urls)),
url(r'^',
include(model_b_router.urls)),
)
|
from
my_app.models import ModelA, ModelB
from my_app.serializers import
ModelASerializer, ModelBSerializer
class
ModelBViewSet(viewsets.ModelViewSet):
queryset =
ModelB.objects.all()
serializer_class =
ModelBSerializer
class
ModelAViewSet(viewsets.ModelViewSet):
queryset =
ModelA.objects.all()
serializer_class =
ModelASerializer
|
from
my_app.models import ModelA, ModelB
class
ModelBSerializer(serializers.ModelSerializer):
class Meta:
model = ModelB
fields = ('field_b_1',)
class
ModelASerializer(serializers.ModelSerializer):
models_b =
ModelBSerializer( many=True )
class Meta:
model = ModelA
fields = ('field_a_1','models_b',)
|
from
rest_framework_nested import routers
model_a_router = routers.SimpleRouter()
model_a_router.register(r'models_a',
views.ModelAViewSet)
model_b_router =
routers.NestedSimpleRouter(model_a_router,
r'models_a', lookup='model_a')
model_b_router.register(r'models_b',
views.ModelBViewSet)
urlpatterns = patterns('',
url(r'^',
include(model_a_router.urls)),
url(r'^',
include(model_b_router.urls)),
)
|
from
django.shortcuts import get_object_or_404
class
ModelBViewSet(viewsets.ModelViewSet):
queryset =
ModelB.objects.all()
serializer_class =
ModelBSerializer
def get_queryset(self):
# only entries that belong to specified
parent
model_a_pk = self.kwargs.get('model_a_pk',
None)
model_a = get_object_or_404(ModelA,
pk=model_a_pk)
models_b =
ModelB.objects.filter(model_a=model_a)
return models_b
def
perform_create(self, serializer):
# get the parent from the lookup field
dashboard_pk
model_a_pk = self.kwargs.get('model_a_pk',
None)
model_a = get_object_or_404(ModelA,
pk=model_a_pk)
serializer.save(model_a=model_a)
class
ModelAViewSet(viewsets.ModelViewSet):
queryset =
ModelA.objects.all()
serializer_class =
ModelASerializer
|
from
my_app.models import ModelA, ModelB
class
ModelBSerializer(serializers.ModelSerializer):
class Meta:
model = ModelB
fields = ('field_b_1',)
class
ModelASerializer(serializers.ModelSerializer):
models_b =
ModelBSerializer( many=True )
class Meta:
model = ModelA
fields = ('field_a_1','models_b',) |
OneToOne
|
class
ModelC(models.Model):
field_c_1 =
models.CharField( max_length=100 )
class ModelD(models.Model):
model_c = models.OneToOneField(
ModelC, related_name='model_d',
blank=True, null=True )
field_d_1 =
models.CharField( max_length=100 )
|
from
rest_framework import routers
model_c_router = routers.SimpleRouter()
model_c_router.register(r'models_c',
views.ModelCViewSet)
model_d_router = routers.SimpleRouter()
model_d_router.register(r'models_d',
views.ModelDViewSet)
urlpatterns = patterns('',
url(r'^',
include(model_c_router.urls)),
url(r'^',
include(model_d_router.urls)),
)
|
from
my_app.models import ModelC, ModelD
from my_app.serializers import
ModelCSerializer, ModelDSerializer
class
ModelCViewSet(viewsets.ModelViewSet):
queryset =
ModelC.objects.all()
serializer_class =
ModelCSerializer
class
ModelDViewSet(viewsets.ModelViewSet):
queryset =
ModelD.objects.all()
serializer_class =
ModelDSerializer
|
from
my_app.models import ModelC, ModelD
class
ModelCSerializer(serializers.ModelSerializer):
class Meta:
model = ModelC
fields = ('field_c_1',)
class
ModelDSerializer(serializers.ModelSerializer):
model_c =
ModelCSerializer( many=False )
class Meta:
model = ModelD
fields = ('field_d_1','model_c',)
|
|
...
|
|
ManyToMany
|
class
ModelE(models.Model):
field_e_1 =
models.CharField( max_length=100 )
class
ModelF(models.Model):
models_e = models.ManyToManyField(
ModelE, related_name='models_f',
blank=True, null=True )
field_f_1 =
models.CharField( max_length=100 )
|
from
rest_framework import routers
model_e_router = routers.SimpleRouter()
model_e_router.register(r'models_e',
views.ModelEViewSet)
model_f_router = routers.SimpleRouter()
model_f_router.register(r'models_f',
views.ModelFViewSet)
urlpatterns = patterns('',
url(r'^',
include(model_e_router.urls)),
url(r'^',
include(model_f_router.urls)),
)
|
from
my_app.models import ModelE, ModelF
from my_app.serializers import
ModelESerializer, ModelFSerializer
class
ModelEViewSet(viewsets.ModelViewSet):
queryset =
ModelE.objects.all()
serializer_class =
ModelESerializer
class
ModelFViewSet(viewsets.ModelViewSet):
queryset =
ModelF.objects.all()
serializer_class =
ModelFSerializer
|
from
my_app.models import ModelE, ModelF
class
ModelFSerializer(serializers.ModelSerializer):
class Meta:
model = ModelF
fields = ('field_f_1',)
class
ModelESerializer(serializers.ModelSerializer):
models_f =
ModelFSerializer( many=True )
class Meta:
model = ModelE
fields = ('field_e_1','models_f',) |
|
...
|
|
- Writable
Foreign
keys in Django Rest Framework
- Writable
nested
serialization (3.x)
- models.py
class
MyModel(models.Model)
field_1 = ...
field_2 = ...
class MyModelSon(models.Model)
mymodel =
models.OneToOneField(MyModel,
related_name='son')
field_a = ...
- serializers.py
- field is not writable:
class
MyModelSonSerializer(serializers.ModelSerializer):
class Meta:
model = MyModelSon
class
MyModelSerializer(serializers.ModelSerializer):
son =
MyModelSonSerializer( read_only=True
)
class Meta:
model = MyModel
fields = ('field_1', 'field_2',
'son')
- field is writable,
- and required:
class
MyModelSonSerializer(serializers.ModelSerializer):
class Meta:
model = MyModelSon
class
MyModelSerializer(serializers.ModelSerializer):
son =
MyModelSonSerializer()
class Meta:
model = MyModel
fields = ('field_1', 'field_2',
'son')
def create(self,
validated_data):
son_data =
validated_data.pop('son')
my_instance =
MyModel.objects.create(**validated_data)
MyModelSon.objects.create(mymodel=my_instance,
**son_data)
return my_instance
- but not required:
class
MyModelSonSerializer(serializers.ModelSerializer):
class Meta:
model = MyModelSon
class
MyModelSerializer(serializers.ModelSerializer):
son =
MyModelSonSerializer( required=False
)
class Meta:
model = MyModel
fields = ('field_1', 'field_2',
'son')
def create(self,
validated_data):
if 'son' in validated_data:
son_data
= validated_data.pop('son')
my_instance
=
MyModel.objects.create(**validated_data)
MyModelSon.objects.create(mymodel=my_instance,
**son_data)
else:
my_instance =
MyModel.objects.create(**validated_data)
return my_instance
- Problemes / Problems:
- AttributeError at ...
'QuerySet' object has no attribute '...'
- Solució / Solution
- Comproveu que la crida al serialitzador la
feu amb / Check that the call to the
serializer contains:
- Exemples / Examples:
- WritableField
- Hexadecimal integers
class
HexIntegerField(serializers.WritableField):
# serialization
def to_native(self,
obj):
return "0x%02x" % (obj)
# deserialization
def from_native(self,
data):
# convert from hex in unicode ("0x02") to
integer (2)
return int(data, 16)
SerializerMethodField
- escriptura / write
- simple field from grandparent (single field
from parent can be done with SlugRelatedField):
- models.py
- serializers.py
class
TotoSerializer(serializers.ModelSerializer):
grandparent_field =
serializers.SerializerMethodField()
def
get_grandparent_field(self,obj):
my_int_field =
obj.parent.grandparent.some_int_field
return
serializers.IntegerField().to_representation(my_int_field)
class Meta:
model = TotoModel
fields = ('grandparent_field',)
- make date
calculations:
class
ServiceEITSerializer(serializers.ModelSerializer):
events =
EventEITSerializer()
# drf 2.x:
#start_time =
serializers.SerializerMethodField('get_start_time')
#end_time =
serializers.SerializerMethodField('get_stop_time')
# drf 3.x:
start_time =
serializers.SerializerMethodField()
end_time =
serializers.SerializerMethodField()
def
get_start_time(self,obj):
min_date = min([event.start for event in
obj.events.all()])
# drf 2.x:
#return
serializers.DateTimeField().to_native(min_date)
# drf 3.x:
return
serializers.DateTimeField().to_representation(min_date)
def
get_stop_time(self,obj):
max_date = max([event.start +
datetime.timedelta(hours=event.duration.hour,minutes=event.duration.minute,seconds=event.duration.second)
for
event in obj.events.all()])
# drf 2.x:
#return
serializers.DateTimeField().to_native(max_date)
# drf 3.x:
return
serializers.DateTimeField().to_representation(max_date)
class Meta:
model = DVB_Service
fields =
('service_id','start_time','end_time','events',)
- aniuat i
filtrat / nested and filtered (ManyToMany)
- How can I apply
a filter to a nested resource in Django
REST framework?
- models.py
class
TotoFill(models.Model):
nom =
models.CharField(max_length=100)
start_date =
models.DateField()
end_date =
models.DateField()
def
__unicode__(self):
return self.nom
class Meta:
ordering = ['-start_date']
class TotoPare(models.Model):
titol =
models.CharField(max_length=100)
toto_fills =
models.ManyToManyField(TotoFill)
start_date =
models.DateField()
end_date =
models.DateField()
class Meta:
ordering = ['-start_date']
- serializers.py
class
TotoFillSerializer(serializers.ModelSerializer):
class Meta:
model = TotoFill
class
TotoPareSerializer(serializers.ModelSerializer):
toto_fillets =
serializers. SerializerMethodField ('toto_fills_no_futurs')
def
toto_fills_no_futurs(self, obj):
# "totopare" exists because it is
automatically created (ManyToMany
through)
qs =
TotoFill.objects.filter(totopare=obj,start_date__lte=date.today())
serializer = TotoFillSerializer(qs,
many=True)
return serializer.data
class Meta:
model = TotoPare
fields =
('id','titol','start_date','end_date','toto_fillets')
- SlugRelatedField
- see
SerializerMethodField to get
grandparent fields instead of parent fields
-> Avi/Grandparent
- models.py (Note: Pare means Father; Fill means
Son)
class
Pare(models.Model):
pare_id =
models.IntegerField(_("pare_id"), unique=True)
class Fill(models.Model):
pare =
models.ForeignKey(Pare,
related_name="fills", blank=True,
null=True)
fill_id =
models.IntegerField(_("fill_id"))
- option 1: preserving names
- serializers.py
class
FillSerializer(serializers.ModelSerializer):
pare =
serializers.SlugRelatedField(slug_field='pare_id',queryset=Pare.objects.all())
class Meta:
model = Fill
fields = ('pare','fill_id',)
- can be used to create an object by sending
a json via POST:
{
"pare": 813,
"fill_id": 1008
}
- option 1.2: groups and users
- serializers.py
from
django.contrib.auth.models import
Group, User
class
UserSerializer(serializers.ModelSerializer):
groups =
serializers.SlugRelatedField(
many=True, slug_field='name',
queryset=Group.objects.all() )
class Meta:
model = User
- views.py
class
UserViewSet(viewsets.ModelViewSet):
queryset =
User.objects.all()
serializer_class =
UserSerializer
def
perform_create(self, serializer):
user = serializer.save()
# set the password
user.set_password(serializer.validated_data['password'])
user.save()
- option 2: changing the name of the serialized
field ('pare'->'toto'; it could be 'pare_id'
instead of 'toto')
- serializers.py
class
FillSerializer(serializers.ModelSerializer):
toto
=
serializers.SlugRelatedField(slug_field='pare_id',
source='pare',
queryset=Pare.objects.all() )
class Meta:
model = DVB_Event
fields = ('toto','fill_id',)
- can be used to create an object by sending
a json via POST:
{
"toto":
813,
"fill_id": 1008
}
- Avi /
Grandparent
- models.py
class
Avi(models.Model):
avi_id = models.IntegerField(_("avi_id"),
unique=True)
class Pare(models.Model):
avi = models.ForeignKey(Avi,
related_name="pares", blank=True,
null=True)
pare_id =
models.IntegerField(_("pare_id"), unique=True)
class Fill(models.Model):
pare =
models.ForeignKey(Pare,
related_name="fills", blank=True,
null=True)
fill_id =
models.IntegerField(_("fill_id"))
- serializers.py
class
FillSerializer(serializers.ModelSerializer):
avi =
serializers.SlugRelatedField(slug_field='avi_id',
source='pare.avi')
class Meta:
model = Fill
fields = ('avi','fill_id',)
- ...
- Allow
null:
- ...
- Views
-
|
|
|
|
|
custom
action
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ detail_route()
def custom_action
|
@list_route()
def custom_action |
@detail_route(methods=['POST',
'DELETE'])
def custom_action |
|
|
|
|
attributes |
methods
(that can be defined)
|
|
get(self,
request)
|
post(self,
request) |
get
|
put, patch |
delete |
|
|
(specified
by "methods")
|
|
inside the table,
the actions provided by:
|
|
generics.GenericAPIView |
- model
- queryset
- serializer_class
- permission_classes
- lookup_field
- paginate_by
- ...
|
|
+
|
get
|
|
|
|
|
|
|
|
=
|
ListAPIView |
generics |
|
post
|
|
|
|
|
|
|
CreateAPIView |
|
|
get
|
|
|
|
|
|
RetrieveAPIView |
|
|
|
put, patch
|
|
|
|
|
UpdateAPIView |
|
|
|
|
delete
|
|
|
|
DestroyAPIView |
get
|
post
|
|
|
|
|
|
|
ListCreateAPIView |
|
|
get
|
put, patch |
|
|
|
|
RetrieveUpdateAPIView |
|
|
get
|
|
delete |
|
|
|
RetrieveDestroyAPIView |
|
|
get
|
put, patch |
delete |
|
|
|
RetrieveUpdateDestroyAPIView |
|
|
|
|
|
|
|
|
MyAPIView
|
viewsets.GenericViewSet |
+
|
list
|
create(self,
request, *args, **kwargs) |
retrieve
|
update,
partial_update
|
delete
|
|
|
|
=
|
ModelViewSet |
viewsets |
list
|
|
retrieve
|
|
|
|
|
|
ReadOnlyModelViewSet |
(x) |
(x) |
(x) |
(x) |
(x) |
(x)
|
(x) |
|
MyViewSet
|
|
|
provided actions
(that can be overwritten) -> |
|
def list(self,
request): |
def create(self,
request): |
def
retrieve(self, request, pk=None): |
def update(self,
request, pk=None):
def partial_update(self, request, pk=None): |
def destroy(self,
request, pk=None): |
|
|
|
|
|
|
|
|
|
|
ListModelMixin |
CreateModelMixin |
RetrieveModelMixin |
UpdateModelMixin |
DestroyModelMixin |
|
|
|
|
|
|
|
|
|
|
mixins
|
|
|
|
|
|
|
-
|
|
+
|
mixins.ViewSetMixin(object)
- as_view()
- initialize_request()
|
mixins.CreateModelMixin(object)
- def create(self,
request,
*args, **kwargs):
serializer =
self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers =
self.get_success_headers(serializer.data)
return Response(serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
- def perform_create(self,
serializer):
serializer.save()
- def get_success_headers(self,data):
...
|
mixins.RetrieveModelMixin(object)
- def retrieve(self,
request,
*args, **kwargs):
instance = self.get_object()
serializer =
self.get_serializer(instance)
return Response(serializer.data)
|
mixins.ListModelMixin(object)
- def list(self,
request,
*args, **kwargs):
queryset =
self.filter_queryset(self.get_queryset())
page =
self.paginate_queryset(queryset)
if page is not None:
serializer =
self.get_serializer(page, many=True)
return
self.get_paginated_response(serializer.data)
serializer =
self.get_serializer(queryset, many=True)
return Response(serializer.data)
|
mixins.UpdateModelMixin(object)
- def update(self,
request,
*args, **kwargs):
partial = kwargs.pop('partial',
False)
instance = self.get_object()
serializer =
self.get_serializer(instance,
data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
- def perform_update(self,
serializer):
serializer.save()
- def partial_update(self,
request,
*args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args,
**kwargs)update
|
mixins.DestroyModelMixin(object)
- def destroy(self,
request,
*args, **kwargs):
instance =
self.get_object()
self.perform_destroy(instance)
return
Response(status=status.HTTP_204_NO_CONTENT)
- def perform_destroy(self,
instance):
instance.delete()
|
=
|
views.APIView(View)
- renderer_classes
- parser_classes
- authentication_classes
- throttle_classes
- permission_classes
- content_negotiation_class
- metadata_class
- versioning_class
- as_view()
- ...
|
generics.GenericAPIView(views.APIView)
|
|
x |
|
|
|
|
generics.CreateAPIView(mixins.CreateModelMixin,
GenericAPIView):
- post(self,
request,
*args, **kwargs):
self.create
|
|
|
x
|
|
|
|
generics.RetrieveAPIView(mixins.RetrieveModelMixin,
GenericAPIView):
- get(self,
request,
*args, **kwargs):
self.retrieve(request, *args,
**kwargs)
|
|
|
|
x
|
|
|
generics.ListAPIView(mixins.ListModelMixin,
GenericAPIView):
- get(self,
request,
*args, **kwargs):
self.list
|
|
|
|
|
x
|
|
generics.UpdateAPIView(mixins.UpdateModelMixin,
GenericAPIView):
- put(self,
request,
*args, **kwargs):
self.update
- patch(self,
request,
*args, **kwargs):
self.partial_update
|
|
|
|
|
|
x
|
generics.DestroyAPIView(mixins.DestroyModelMixin,
GenericAPIView):
- delete(self,
request,
*args, **kwargs):
self.destroy
|
|
x
|
|
x
|
|
|
generics.ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
- get(self,
request,
*args, **kwargs):
self.list
- post(self,
request,
*args, **kwargs):
self.create
|
|
|
x
|
|
x
|
|
generics.RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
GenericAPIView):
- get(self,
request,
*args, **kwargs):
self.retrieve
- put(self,
request,
*args, **kwargs):
self.update
- patch(self,
request,
*args, **kwargs):
self.partial_update
|
|
|
x
|
|
|
x
|
generics.RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
- get(self,
request,
*args, **kwargs):
self.retrieve
- delete(self,
request,
*args, **kwargs):
self.destroy
|
|
|
x
|
|
x
|
x
|
generics.RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
- get(self,
request,
*args, **kwargs):
self.retrieve
- put(self,
request,
*args, **kwargs):
self.update
- patch(self,
request,
*args, **kwargs):
self.partial_update
- delete(self,
request,
*args, **kwargs):
self.destroy
|
|
|
|
|
|
|
CustomAPIView(GenericAPIView):
- # e.g.: view
for django-solo
- get(self,
request,
*args, **kwargs):
...
- post(self,
request,
*args, **kwargs):
...
- put(self,
request,
*args, **kwargs):
...
- patch(self,
request,
*args, **kwargs):
...
- delete(self,
request,
*args, **kwargs):
...
|
viewsets.GenericViewSet(ViewSetMixin,
generics.GenericAPIView):
pass
|
x
|
x
|
x
|
x
|
x
|
viewsets.ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet): |
|
x
|
x
|
|
|
viewsets.ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet): |
|
|
|
|
|
CustomViewSet(...,
GenericViewSet):
|
|
viewsets.ViewSet(ViewSetMixin,
views.APIView):
pass
|
|
|
|
|
|
|
- APIView
- It can be
used, e.g., to perform an action, not directly
related to ORM (the classical creation of database
entries)
- Funcions: get(), post(), patch(), put(), delete()
must be implemented
- must return a Response:
from
rest_framework.response import Response
from rest_framework.views import APIView
class MyAPIView(APIView):
def get(self, request,
*args, **kwargs):
...
return Response(...)
- APIView does not have serializer_class;
generics.GenericAPIVIew has it (and swagger can show
it on docs)
- GenericAPIView
- MyAPIView (based
on generics.GenericAPIView) examples ((?) when using
plain GenericAPIView, no get, post, get, put, patch
are implemented, and need to be defined):
class
TotoAPIView(generics.GenericAPIView):
# input serializer
serializer_class =
TotoInputSerializer
# define post method
def post(self,
request,
format=None):
input_serializer =
TotoInputSerializer(data=request.DATA)
if
input_serializer.is_valid():
...
return
...
else:
return
Response(input_serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
class
TotoAPIView(generics.GenericAPIView):
# output serializer
serializer_class =
TotoOutputSerializer
# define get method
# this could also be
achieved by using generics.ListAPIView (no
need to define get, then)
def get(self,
request,
*args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset,
many=True)
return Response(serializer.data)
class
TotoAPIView(generics.GenericAPIView):
# output serializer
serializer_class =
TotoOutputSerializer
# define get method
# this could also be
achieved by using generics.ListAPIView (no
need to define get, then)
def get(self,
request,
*args, **kwargs):
#
build temporary object
obj
= dict()
obj[' first_numer '] =
MyModel.objects.count()
obj[' second_numer ']
=
MyModel.objects.filter(is_active=True).count()
#
serialize it
serializer = self.get_serializer(obj)
return Response(serializer.data)
class
OtherModelSerializer(serializers.ModelSerializer):
...
class Mixed OutputSerializer(serializers.Serializer):
first_number
= serializers.IntegerField()
results = OtherModelSerializer(many=True)
class
TotoAPIView(generics.GenericAPIView):
# output serializer (just
for swagger, because recursive serialization
does not work with mixed ORM and non-ORM)
serializer_class =
MixedOutputSerializer
# define get method
# this could also be
achieved by using generics.ListAPIView (no
need to define get, then)
def get(self,
request,
*args, **kwargs):
other_queryset = OtherModel.objects.all()
other_serializer =
OtherModelSerializer(queryset, many=True)
return Response({'first_number':
MyModel.objects.count() ,
'results':other_serializer.data})
- Example with
django-solo
- my_project/settings.py
INSTALLED_APPS = (
...
'solo',
)
- my_app/models.py
from solo.models
import SingletonModel
class PricingLimits(SingletonModel):
...
- my_app/serializers.py
class
MySoloModelSerializer(serializers.ModelSerializer):
class Meta:
model = MySoloModel
- my_app/views.py
class
MySoloModelView(generics.GenericAPIView):
serializer_class =
MySoloModelSerializer
def get(self, request):
instance = MySoloModel.get_solo()
serializer = self.get_serializer(instance)
return Response(serializer.data)
- my_app/urls.py
url(r'^my_view/$',
views.MySoloModelView.as_view(),
name='mysolomodel-detail' ),
- ViewSets
- See general View
table
- ViewSets are recommended if you are going to use a
router
- If you are not happy with predefined ModelViewSet
(list, create, retrieve, update, destroy) or
ReadOnlyModelViewSet (list, retrieve), you can
define your own ViewSet by combining mixins, that
provide the desired features. For instance, you can
define CreateUpdateViewSet with only create and
update.
- ModelViewSet
examples:
from rest_framework
import viewsets, permissions
class
ParticipantChallengeViewSet(viewsets.ModelViewSet):
"""
Relationship (and status)
between participants and challenges.
"""
queryset =
ParticipantChallenge.objects.all()
serializer_class =
ParticipantChallengeSerializer
permission_classes =
(permissions.IsAuthenticated,)
# search by
<challenge> (specified on the url)
instead of <pk>
lookup_field = 'challenge'
# as participant pk is not
explicitly provided,
# get
it from the user on the request
def pre_save(self,
obj):
obj.participant = self.request.user
# for list: filter by the
name of the participant
def get_queryset(self):
p =
self.request.user
#return p.challenge_set.all()
return
ParticipantChallenge.objects.filter(participant=p)
- MyViewSet (based
on user-constructed ViewSet) examples (with a usage
example):
class
CreateViewSet(mixins.CreateModelMixin,
viewsets.GenericViewSet):
pass
class
CreateUpdateViewSet(mixins.CreateModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
pass
class
CreateRetrieveUpdateViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
pass
class
UpdateViewSet(mixins.UpdateModelMixin,
viewsets.GenericViewSet):
pass
class
ListUpdateViewSet(mixins.ListModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
pass
class
ListViewSet(mixins.ListModelMixin,
viewsets.GenericViewSet):
pass
# example of usage of
user-defined viewsets:
class
TotoCreateUpdateViewSet(CreateUpdateViewSet):
queryset =
Toto.objects.all()
serializer_class =
TotoSerializer
# overwrite create method
def create(self,
request,
*args, **kwargs):
serializer =
self.get_serializer(data=request.DATA,
files=request.FILES)
if
serializer.is_valid():
self.pre_save(serializer.object)
#set_password
serializer.object.set_password(serializer.data['password'])
self.object = serializer.save(force_insert=True)
self.post_save(self.object,
created=True)
- Mixins
-
|
mixin
|
function
|
call to
get_serializer
|
deserialization
|
CreateModelMixin
|
create
|
get_serializer(data=request.data)
|
UpdateModelMixin |
update |
get_serializer(instance,
data=request.data,
partial=partial) |
serialization
|
ListModelMixin
|
list
|
get_serializer(page,
many=True)
get_serializer(queryset, many=True)
|
RetrieveModelMixin
|
retrieve
|
get_serializer(instance)
|
|
DestroyModelMixin
|
destroy
|
-
|
- Marking
extra
methods for routing (@action)
- action
decorator replaces list_route and detail_route
(DRF 3.9)
@detail_route() ->
@action(detail=True)
- @list_route()
-> @action(detail=False)
@action(...)
- detail
- methods
methods=["get"]
methods=["post"]
permission_classes=(permissions.AllowAny,)
serializer_class=MyOutputSerializer
- ...
- action with extra path paramters
- Django
REST Framework - pass extra parameter to
actions
@action(detail=True,
methods=['delete'],
url_path='contacts/(?P<phone_pk>[^/.]+)')
def delete_phone(self, request, phone_pk,
pk=None):
contact =
self.get_object()
phone =
get_object_or_404(contact.phone_qs,
pk=phone_pk)
phone.delete()
return Response(..,
status=status.HTTP_204_NO_CONTENT)
-
|
default
method
|
URL
|
from
rest_framework.decorators import
action
@action(detail=True, methods=['post'] )
def
set_password(self,
request,
*args,
**kwargs):
my_object =
self.get_object()
...
|
GET
|
^users/{pk}/set_password/$
|
#
when lookup_field='toto_field':
@action( detail=True,
methods=['post'])
def
set_password(self,
request,
*args, **kwargs):
my_object =
self.get_object()
...
@action(
detail=True,
methods=['post'])
def
set_password(self,
request,
toto_field=None):
my_object =
self.get_object()
... |
GET
|
^users/{toto_field}/set_password/$ |
from
rest_framework.decorators import
action
@action(
detail=False )
def
recent_users(self, request, *args, **kwargs):
qs = self.get_queryset()
qs_filtered =
qs.filter(...)
serializer =
self.get_serializer(qs_filtered,
many=True)
response =
Response(serializer.data)
return response
|
GET |
^users/recent_users/$
|
- POST example to perform an action
import logging
from rest_framework import viewsets
from rest_framework.decorators import
action
from rest_framework.response import
Response
from rest_framework.exceptions import
APIException
from my_app.models import MyModel
from my_app.serializers import
MyModelSerializer
logger = logging.getLogger(__name__)
class
MyModelViewSet(viewsets.ModelViewSet):
queryset =
MyModel.objects.all()
serializer_class =
MyModelSerializer
lookup_field = 'name'
@action(detail=True,
methods=['post'])
def do_something(self,
request, *args, **kwargs):
instance = self.get_object()
try:
instance.do_something()
return
Response({'status': 'something done'})
except Exception as e:
logger.error("[MyModelViewSet.do_something]
Exception: %s" % e)
raise
APIException("Error when doing something:
%s" % e)
- Problemes / Problems
- @detail_route not working with
lookup_field
- Type error: my_function() got an
unexpected keyword argument ...
- lookup_field
error
with detail_route
- Solució / Solution
- check that your function has
kwargs parameter:
@detail_route()
def my_function(self,
request, *args, **kwargs):
- deprecated
- GET:
- POST:
@action()
def set_password(self, request,
pk=None)
@action(permission_classes=[IsAdminOrIsSelf])
@action(methods=['POST', 'DELETE'])
- Renderers
- Format
suffixes (to choose the render based on request
suffix)
- Examples
- XMLRenderer
- django-rest-framework-xml
- XML
in Python
- myapp/renderers.py
"""
Provides XML rendering support.
"""
from __future__ import unicode_literals
from django.utils import six
from django.utils.xmlutils import
SimplerXMLGenerator
from django.utils.six.moves import
StringIO
from django.utils.encoding import
force_text
from rest_framework.renderers import
BaseRenderer
from lxml import etree
class XMLRenderer(BaseRenderer):
"""
Renderer which
serializes to XML.
"""
media_type =
'application/xml'
format = 'xml'
charset = 'utf-8'
item_tag_name =
'list-item'
set_item_tag_name_from_plural = True
def render(self, data,
accepted_media_type=None,
renderer_context=None,
root_tag_name='root'):
"""
Renders data into serialized XML.
"""
if data is None:
return ''
xml = etree.Element(root_tag_name)
self._to_xml(xml, data)
# convert to string
return etree.tostring(xml,
encoding='utf-8', xml_declaration=True,
pretty_print=True).decode('utf-8')
def _to_xml(self, xml,
data):
if isinstance(data, (list, tuple)):
for item in data:
item_tag_name
= item.pop('#tag',self.item_tag_name)
child
= etree.SubElement(xml, item_tag_name)
self._to_xml(child, item)
elif isinstance(data, dict):
for key, value in six.iteritems(data):
if key.startswith('@'):
# attribute
xml.set(key[1:],
value)
elif key=='#text':
# text
xml.text
= value
else:
# child
child
= etree.SubElement(xml, key)
#
if value is a list, we remove 's' from
key, to be used as key for each item,
#
provided that it is not overwritten by
"#tag"
if
self.set_item_tag_name_from_plural:
self.item_tag_name
= key[:-1]
self._to_xml(child,
value)
elif data is None:
pass
else:
xml.text = force_text(data)
- toto.json
{
"primer": {
"@attr1": "value_1",
"@attr2": "value_2",
"#text": "mytext"
},
"segon": 1,
"mylist": [
{"#tag": "myitem", "l1_a": "l1_a_value",
"l1_b": "l1_b_value"},
{"#tag": "myotheritem","l2_a":
"l2_a_value", "l2_b": "l2_b_value"}
],
"myelements": [
{"el1_a": "el1_a_value", "el1_b":
"el1_b_value"},
{"el2_a": "el2_a_value", "el2_b":
"el2_b_value"}
]
}
- test_xmlrenderer.py
# -*- coding:
utf-8 -*-
import django
from django.conf import settings
settings.configure()
django.setup()
import json
from myapp.renderers import XMLRenderer
json_path = "toto.json"
with open(json_path, "r") as f:
obj = json.load(f)
xml = XMLRenderer().render(obj,
root_tag_name="myroot")
print(xml)
- output:
<?xml
version='1.0' encoding='utf-8'?>
<myroot>
<segon>1</segon>
<primer attr1="value_1"
attr2="value_2">mytext</primer>
<mylist>
<myitem>
<l1_a>l1_a_value</l1_a>
<l1_b>l1_b_value</l1_b>
</myitem>
<myotheritem>
<l2_a>l2_a_value</l2_a>
<l2_b>l2_b_value</l2_b>
</myotheritem>
</mylist>
<myelements>
<myelement>
<el1_a>el1_a_value</el1_a>
<el1_b>el1_b_value</el1_b>
</myelement>
<el1_>
<el2_b>el2_b_value</el2_b>
<el2_a>el2_a_value</el2_a>
</el1_>
</myelements>
</myroot>
- XLSRenderer
- admin
action to render to a file
content =
JSONRenderer().render(serializer.data)
- Custom
renderers
- renderers.py
from
rest_framework.renderers import BaseRenderer
class UriListRenderer(BaseRenderer):
# to be specified in Accept
http header
media_type =
'text/uri-list'
# shown in api browser and
in queryset ?format=uri-list
format = 'uri-list'
- views.py
from
rest_framework.settings import DEFAULTS
from renderers import UriListRenderer
class TotoView(View):
renderers =
DEFAULTS['DEFAULT_RENDERER_CLASSES'] +
(UriListRenderer,)
class TotoViewSet(viewsets.ModelViewSet):
# order is important: first
entry determines the default renderer
renderer_classes =
(JSONRenderer, BrowsableAPIRenderer,
UriListRenderer,)
- Client
curl
-X GET -H 'Accept: text/uri-list'
...
- Examples from tutorial (2.3.x):
|
project
|
application
|
|
test
|
settings.py
|
urls.py
|
models.py
|
serializers.py
|
urls.py
|
views.py
|
|
Quickstart
|
DATABASES...
INSTALLED_APPS =
(
...
'rest_framework', )
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
'PAGINATE_BY': 10 } |
from django.conf.urls
import patterns, url, include
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
urlpatterns = patterns('',
url(r'^', include(router.urls)),
url(r'^api-auth/',
include('rest_framework.urls',
namespace='rest_framework')),
)
|
-
|
from rest_framework
import serializers
from django.contrib.auth.models import User, Group
class
UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model =
User
fields =
('url', 'username', 'email', 'groups') |
|
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or
edited.
"""
queryset = User.objects.all() serializer_class = UserSerializer |
|
curl
-H 'Accept: application/json; indent=4' -u
admin:password http://127.0.0.1:8000/users/
|
Tutorial
function based views
(snippets)
|
INSTALLED_APPS = (
...
'rest_framework',
'snippets',
) |
urlpatterns
= patterns('',
url(r'^',
include('snippets.urls')),
) |
class
Snippet(models.Model):
created =
models.DateTimeField(auto_now_add=True)
...
|
class
SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields =
('id', 'title', 'code', 'linenos', 'language',
'style') |
urlpatterns =
patterns('snippets.views',
url(r'^snippets/$',
'snippet_list'),
url(r'^snippets/(?P<pk>[0-9]+)/$',
'snippet_detail'),
)
urlpatterns = format_suffix_patterns(urlpatterns)
|
@api_view(['GET',
'POST'])
def snippet_list(request):
...
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
... |
|
|
Tutorial
3:
class based views
|
|
|
from
snippets import views
urlpatterns = patterns('',
url(r'^snippets/$',
views.SnippetList.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$',
views.SnippetDetail.as_view()),
)
|
from
rest_framework.views import APIView
class SnippetList(APIView):
def get(self, request,
format=None):
...
def post(self, request,
format=None):
...
class SnippetDetail(APIView):
def get_object(self, pk):
...
def get(self, request, pk,
format=None):
...
def put(self, request, pk,
format=None):
...
def delete(self, request, pk,
format=None):
...
|
|
|
mixins, generics
|
|
|
from snippets.models
import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class =
SnippetSerializer
def get(self, request, *args,
**kwargs):
return
self.list(request, *args, **kwargs)
def post(self, request, *args,
**kwargs):
return
self.create(request, *args, **kwargs)
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class =
SnippetSerializer
def get(self, request, *args,
**kwargs):
return
self.retrieve(request, *args, **kwargs)
def put(self, request, *args,
**kwargs):
return
self.update(request, *args, **kwargs)
def delete(self, request, *args,
**kwargs):
return
self.destroy(request, *args, **kwargs)
|
|
|
generics
|
|
|
from snippets.models
import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class =
SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class =
SnippetSerializer
|
|
|
Tutorial
4:
Authentication & Permissions
|
|
|
class
Snippet(models.Model):
...
owner = models.ForeignKey('auth.User',
related_name='snippets')
highlighted = models.TextField()
def save(self, *args, **kwargs):
...
|
from
django.contrib.auth.models import User
...
class
SnippetSerializer(serializers.ModelSerializer):
owner
= serializers.Field(source='owner.username')
class Meta:
model =
Snippet
fields =
('id', 'title', 'code', 'linenos', 'language',
'style', 'owner')
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model =
User
fields =
('id', 'username', 'snippets')
|
urlpatterns =
patterns('',
...
url(r'^users/$',
views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$',
views.UserDetail.as_view()),
...
)
|
from
django.contrib.auth.models import User
from snippets.serializers import UserSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class =
SnippetSerializer
def
pre_save(self, obj):
obj.owner
= self.request.user
class
SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class =
SnippetSerializer
def
pre_save(self, obj):
obj.owner
= self.request.user
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
|
|
|
authentication
|
|
|
|
|
# login/logout at top
right corner
urlpatterns += patterns('',
url(r'^api-auth/',
include('rest_framework.urls',
namespace='rest_framework')),
) |
from rest_framework
import permissions
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class =
SnippetSerializer
permission_classes
=
(permissions.IsAuthenticatedOrReadOnly,)
... class
SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class =
SnippetSerializer
permission_classes
=
(permissions.IsAuthenticatedOrReadOnly,)
|
|
|
permissions
|
|
|
|
|
|
from
snippets.permissions import IsOwnerOrReadOnly
class
SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class =
SnippetSerializer
permission_classes =
(permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
...
|
from rest_framework
import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self,
request, view, obj):
# Read
permissions are allowed to any request,
# so
we'll always allow GET, HEAD or OPTIONS requests.
if
request.method in
permissions.SAFE_METHODS:
return True
# Write
permissions are only allowed to the owner of the
snippet
return
obj.owner == request.user
|
|
Tutorial
5:
Relationships & Hyperlinked APIs
|
|
|
|
|
urlpatterns =
format_suffix_patterns(patterns('snippets.views',
url(r'^$', 'api_root'),
url(r'^snippets/$',
views.SnippetList.as_view(),
name='snippet-list'),
url(r'^snippets/(?P<pk>[0-9]+)/$',
views.SnippetDetail.as_view(),
name='snippet-detail'),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
views.SnippetHighlight.as_view(),
name='snippet-highlight'),
url(r'^users/$',
views.UserList.as_view(),
name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$',
views.UserDetail.as_view(),
name='user-detail')
))
|
from rest_framework
import renderers
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
@api_view(('GET',))
def api_root(request,
format=None):
return Response({
'users':
reverse('user-list', request=request,
format=format),
'snippets': reverse('snippet-list', request=request,
format=format)
})
|
|
|
highlighted
|
|
|
|
|
...
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
views.SnippetHighlight.as_view()),
|
from rest_framework
import generics
class SnippetHighlight(generics.GenericAPIView):
queryset = Snippet.objects.all()
renderer_classes =
(renderers.StaticHTMLRenderer,)
def get(self, request, *args,
**kwargs):
snippet =
self.get_object()
return
Response(snippet.highlighted)
|
|
|
hyperlinked
|
|
|
|
class
SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner =
serializers.Field(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight',
format='html')
class Meta:
model =
Snippet
fields =
('url', 'highlight', 'owner',
'title',
'code', 'linenos', 'language', 'style')
class
UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True,
view_name='snippet-detail')
class Meta:
model =
User
fields =
('url', 'username', 'snippets')
|
|
|
|
|
Tutorial
6:
ViewSets & Routers
|
|
|
|
|
from django.conf.urls
import patterns, url, include
from snippets import views
from rest_framework.routers import DefaultRouter
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)
# The API URLs are now determined automatically by
the router.
# Additionally, we include the login URLs for the
browseable API.
urlpatterns = patterns('',
url(r'^', include(router.urls)),
url(r'^api-auth/',
include('rest_framework.urls',
namespace='rest_framework'))
)
|
from rest_framework
import viewsets
from rest_framework.decorators import link
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically
provides `list` and `detail` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
class SnippetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically
provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
Additionally we also provide an
extra `highlight` action.
"""
queryset = Snippet.objects.all()
serializer_class =
SnippetSerializer
permission_classes =
(permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
@link(renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request,
*args, **kwargs):
snippet =
self.get_object()
return
Response(snippet.highlighted)
def pre_save(self, obj):
obj.owner
= self.request.user
|
|
|
|
settings.py
|
urls.py |
models.py |
serializers.py |
urls.py |
views.py |
permissions.py
|
test |
|
project
|
application
|
- Example: override the save method to add an action before
it (Django
signals
vs. custom save()-method) (Django
tips:
auto-populated fields) (Django
signals)
- models.py
class Toto(models.Model):
def save(self, *args,
**kwargs):
# put
some actions here:
# then
call the parent save:
super(Toto, self).save(*args, **kwargs)
- Example: update DNI
on an extended user
identified by request (instead of pk):
- models.py:
class
ModifiedUser(AbstractUser):
dni =
models.CharField(max_length=9)
- serializers.py
class
ModifiedUserUpdateSerializer(serializers.ModelSerializer):
class Meta:
model =
ModifiedUser
fields
= ('dni',)
- views.py
class
ModifiedUserUpdateAPIView(generics.UpdateAPIView):
"""
Update the user, according to
the authenticated credentials.
"""
queryset =
ModifiedUser.objects.all()
serializer_class =
ModifiedUserUpdateSerializer
permission_classes =
(permissions.IsAuthenticated,)
# as modifieduser pk is not
explicitly provided,
# get it from the user on the
request
def get_object(self):
p =
self.request.user
try:
return
ModifiedUser.objects.get(pk=p.id)
except
Participant.DoesNotExist:
raise
Http404
# define a replacement for
api_root, as the DefaultRouter (which generates
api_root)
# cannot register ViewSet entries without "-list"
in the URL name (in this case, name,
# specified in urls.py and referenced here as
reverse, contains "-detail")
@api_view(('GET',))
def api_arrel(request, format=None):
return Response({
...
'update_modifieduser':
reverse('updatemodifieduser-detail',
request=request, format=format),
})
- urls.py
urlpatterns = patterns('',
url(r'^update_modifieduser/$',
views.ModifiedUserUpdateAPIView.as_view(),
name='updatemodifieduser-detail' ),
url(r'^api_arrel/$',
'my_app.views.api_arrel'),
)
- Example: action on a queryset
- Example: action on a single object:
- Example: toto1/{toto2} (instead of default toto1/{pk});
returns only entries owned by user that performs the query (
curl -u
usuari:contrasenya )
- models.py
class Toto(models.Model):
user = models.ForeignKey(User)
toto2 =
models.ForeignKey(Toto2)
...
- views.py
class
TotoViewSet(viewsets.ModelViewSet):
queryset = Toto.objects.all()
serializer_class =
TotoSerializer
permission_classes =
(permissions.IsAuthenticated,)
lookup_field
= 'toto2'
# when creating an entry,
automatically put the user on the object,
according to the request
def pre_save(self,
obj):
obj.user = self.request.user
# only entries that belong to
user
def get_queryset(self):
user =
self.request.user
return
user.userchallenge_set.all()
- urls.py
router.register(r'userchallenges',
views.UserChallengeViewSet)
- Example: creation of a user with a given password
- views.py
class
CreateViewSet(mixins.CreateModelMixin,
viewsets.GenericViewSet):
pass
class UserCreateViewSet(CreateViewSet):
queryset = User.objects.all()
serializer_class =
UserSerializer
def create(self, request,
*args, **kwargs):
serializer =
self.get_serializer(data=request.DATA,
files=request.FILES)
if
serializer.is_valid():
self.pre_save(serializer.object)
#set_password
serializer.object.set_password(serializer.data['password'])
self.object = serializer.save(force_insert=True)
self.post_save(self.object,
created=True)
#headers
= self.get_success_headers(serializer.data)
#return
Response(serializer.data,
status=status.HTTP_201_CREATED, headers=headers)
return
Response(serializer.data,
status=status.HTTP_201_CREATED)
return
Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
- urls.py
router.register(r'new_user',
views.UserCreateViewSet)
- Examples
(2.2.x):
|
models.py
|
resources.py
|
forms.py
|
views.py
|
urls.py
|
Sandbox
Root
API |
-
|
-
|
-
|
class
Sandbox(View):
def get(self, request): |
-
|
Blog
Posts
API
|
class
BlogPost(models.Model):
class Comment(models.Model):
|
class
BlogPostResource(ModelResource):
model = BlogPost
class CommentResource(ModelResource):
model = Comment
|
-
|
-
|
ListOrCreateModelView.as_view(resource=BlogPostResource)
InstanceModelView.as_view(resource=BlogPostResource)
ListOrCreateModelView.as_view(resource=CommentResource)
InstanceModelView.as_view(resource=CommentResource)
|
Getting
Started
- Model Views |
class
MyModel(models.Model): |
class
MyModelResource(ModelResource):
model =
MyModel |
-
|
-
|
ListOrCreateModelView.as_view(resource=MyModelResource)
InstanceModelView.as_view(resource=MyModelResource)
|
Object
Store
API
|
-
|
-
|
-
|
class
ObjectStoreRoot(View):
def get(self, request):
def post(self, request):
class StoredObject(View):
def get(self, request):
def put(self, request):
def delete(self, request):
|
ObjectStoreRoot.as_view()
StoredObject.as_view()
|
Code
Highlighting
API
|
-
|
-
|
class
PygmentsForm(forms.Form): |
class
HTMLRenderer(BaseRenderer):
media_type
= 'text/html'
class PygmentsRoot(View):
form
= PygmentsForm
def get(self, request):
def post(self, request):
class PygmentsInstance(View):
renderers
= (HTMLRenderer,)
def get(self, request,
unique_id):
def
delete(self, request,unique_id):utilitats |
PygmentsRoot.as_view()
PygmentsInstance.as_view()
|
Getting
Started
- VIews
|
-
|
-
|
class
MyForm(forms.Form): |
class
ExampleView(View):
def get(self, request):
class AnotherExampleView(View):
form
= MyForm
def get(self, request, num):
def post(self, request,
um): |
ExampleView.as_view()
AnotherExampleView.as_view()
|
Using
Django REST framework Mixin classes |
|
|
|
from djangorestframework.renderers
import DEFAULT_RENDERERS
from rest_framework.settings import
DEFAULTS
class ExampleView(ResponseMixin, View):
renderers = DEFAULT_RENDERERS
renderers = DEFAULTS['DEFAULT_RENDERER_CLASSES']
def get(self, request):
url
= reverse('mixin-view', request=request)
response
= Response(200, {'description':
'Some example content',
'url':
url})
return
self.render(response)
|
|
|
models.py
|
resources.py |
forms.py |
views.py |
urls.py |
- [djcode/mysite/mysite/]settings.py
-
INSTALLED_APPS = (
...,
'djangorestframework'
)
- Example "Getting
started
- Views"
- cd mysite
- python manage.py startapp resourceexample
- settings.py
- urls.py
...
# rest
from resourceexample.views import ExampleView,
AnotherExampleView
urlpatterns = patterns('',
url(r'^$',ExampleView.as_view(),
name='example-resource'),
url(r'^(?P<num>[0-9]+)/$',
AnotherExampleView.as_view(),
name='another-example'),
...
)
- cd resourceexample
- forms.py
from django import
forms
class MyForm(forms.Form):
foo =
forms.BooleanField(required=False)
bar =
forms.IntegerField(help_text='Must be an
integer.')
baz =
forms.CharField(max_length=32, help_text='Free
text. Max length 32 chars.')
- views.py
from
django.core.urlresolvers import reverse
from rest_framework .views
import View
from rest_framework .response
import Response
from rest_framework import status
from resourceexample.forms import MyForm
class ExampleView(View):
"""
A basic read-only view that
points to 3 other views.
"""
def get(self, request):
"""
Handle GET requests, returning a list of URLs
pointing to 3 other views.
"""
return {"Some other resources":
[reverse('another-example',
kwargs={'num':num}) for num in range(3)]}
class AnotherExampleView(View):
"""
A basic view, that can be
handle GET and POST requests.
Applies some simple form
validation on POST requests.
"""
form = MyForm
def get(self, request,
num):
"""
Handle GET requests.
Returns a simple string indicating which view
the GET request was for.
"""
if
int(num) > 2:
return
Response(status.HTTP_404_NOT_FOUND)
return "GET request to AnotherExampleResource
%s" % num
def post(self, request,
num):
"""
Handle POST requests, with form validation.
Returns a simple string indicating what
content was supplied.
"""
if
int(num) > 2:
return
Response(status.HTTP_404_NOT_FOUND)
return "POST request to AnotherExampleResource
%s, with content: %s" % (num,
repr(self.CONTENT))
- Test:
http://127.0.0.1:8000/
- with Firefox Poster
plug-in
- with curl
from command line:
curl -X GET http://127.0.0.1:8000/ -H
'Accept: text/plain'
curl -X POST --data 'foo=true'
http://127.0.0.1:8000/0/
curl -X POST -H 'Content-Type:
application/json' --data-binary '{"foo":true}' http://127.0.0.1:8000/0/
- Other examples
- ...
- Tastypie
- Documentation
- Info:
# easy-install django-tastypie
django-admin.py startproject project_name
- [djcode/project_name/]settings.py
-
INSTALLED_APPS = (
...,
'tastypie'
)
- Example (ModelResource)
- cd project_name
- python manage.py startapp llibres
- settings.py
INSTALLED_APPS = (
...,
'llibres'
)
- urls.py
from
django.conf.urls.defaults import patterns,
include, url
urlpatterns = patterns('',
(r'', include('llibres.urls')),
...
)
- cd llibres
- models.py
from django.db import
models
class Llibre(models.Model):
titol =
models.CharField(max_length=30)
nombre_pagines =
models.IntegerField()
- api.py
from
tastypie.resources import ModelResource from
tastypie.authentication import
BasicAuthentication from
tastypie.authorization import Authorization #from
tastypie.authorization import
DjangoAuthorization
from llibres.models import Llibre
class LlibreResource(ModelResource):
class Meta:
queryset = Llibre.objects.all()
resource_name = 'recurs_llibre'
authentication = BasicAuthentication()
authorization = Authorization()
#
to use authorization from permissions set by
Django admin:
#authorization = DjangoAuthorization()
- urls.py
from
django.conf.urls.defaults import patterns,
include, url
from api import LlibreResource
llibre_resource = LlibreResource()
urlpatterns = patterns('',
(r'^api/',
include(llibre_resource.urls)),
...
)
- Test:
http://127.0.0.1:8000/api/recurs_llibre/
- get all the objects:
curl
"http://127.0.0.1:8000/api/recurs_llibre/"
- get the schema:
curl
"http://127.0.0.1:8000/api/recurs_llibre/schema/?format=json"
- add an entry:
curl -i -H "Content-Type:
application/json" -X POST --data '{"titol":
"ei", "nombre_pagines": 2}'
http://127.0.0.1:8000/api/recurs_llibre/
- query one specific entry:
- from command line:
curl
"http://127.0.0.1:8000/api/recurs_llibre/1/"
- from browser:
http://127.0.0.1:8000/api/recurs_llibre/1/?format=json
- add another entry:
curl -i -H "Content-Type:
application/json" -X POST --data '{"titol":
"ei2", "nombre_pagines": 4}'
http://127.0.0.1:8000/api/recurs_llibre/
- delete the first entry (will return "
HTTP/1.0
204 NO CONTENT "):
curl -i -X DELETE
http://127.0.0.1:8000/api/recurs_llibre/1/
- use user
authentication:
- curl --user usuari:contrasenya
...
- Example (ModelResource) with 2 resources, validation:
- cd llibres
- api.py
from
tastypie.resources import ModelResource
from tastypie.authorization import
Authorization
from llibres.models import Llibre
from django.contrib.auth.models import User
from tastypie.validation import Validation
class LlibreResource(ModelResource):
class Meta:
queryset = Llibre.objects.all()
resource_name = 'recurs_llibre'
authorization = Authorization()
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'auth/user'
excludes = ['email', 'password',
'is_superuser']
#
Add it here.
validation = Validation()
- urls.py
from
django.conf.urls.defaults import patterns,
include, url
from tastypie.api import Api
from api import LlibreResource, UserResource
v1_api = Api(api_name='v1')
v1_api.register(LlibreResource())
v1_api.register(UserResource())
urlpatterns = patterns('',
(r'^api/',
include(v1_api.urls)),
)
- Test:
curl -i "http://127.0.0.1:8000/api/v1/recurs_llibre/schema/?format=json"
- ...
curl -i
"http://127.0.0.1:8000/api/v1/auth/user/?format=json"
- Example (Resource) (Using
Tastypie
With Non-ORM Data Sources)
|
Loggers
|
- Logging (dev)
- /usr/lib64/python2.7/logging/handlers.py
- (Semi-)correct
handling of log rotation in multiprocess Python
applications
- when using several workers, use system logrotate
instead of Django RotatingFileHandler
- Exemple / Example:
- my_project/settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers':
False,
'formatters': {
'estandard': {
'format'
: "[%(asctime)s.%(msecs)03d]
%(levelname)s [%(name)s:%(lineno)s] %(message)s",
'datefmt'
: "%d/%b/%Y %H:%M:%S"
},
},
'handlers': {
'consola':{
'level':'DEBUG',
'class':'logging.StreamHandler',
'formatter':
'estandard'
},
'fitxer': {
'level':
'DEBUG',
'class':
'logging.FileHandler',
'filename':
os.path.join(BASE_DIR, 'debug.log'),
'formatter':
'estandard',
},
'fitxer_rotacio': {
'level':
'DEBUG',
'class':'logging.handlers.RotatingFileHandler',
'filename':
os.path.join(BASE_DIR, 'debug_rotacio.log'),
# to add numerical external
IP address to filename:
# import socket
# 'filename':
os.path.join(BASE_DIR, 'debug_rotacio.log' %
socket.gethostbyname_ex(socket.gethostname())[2][0]),
'formatter': 'estandard',
'maxBytes':
50000,
'backupCount':
20,
},
},
'loggers': {
'my_app': {
'handlers':
['consola','fitxer'],
'level':
'DEBUG',
'propagate':
True,
},
'my_second_app': {
'handlers':
['consola','fitxer_rotacio'],
'level':
'DEBUG',
'propagate':
True,
},
},
}
- my_app/my_file.py
import logging
logger = logging.getLogger(__name__)
...
logger.debug("My message with
param {}", myparam) # new syntax
logger.info("My message")
logger.warning("My
message")
logger.error("My message")
logger.exception("My message")
logger.critical("My
message")
- Pylint
- Problemes / Problems
- Rotació i permisos
/ Rotation and permissions
- Django log rotating and
log file ownership
- Solució / Solution: GroupWriteRotatingFileHandler
- Does python
logging.handlers.RotatingFileHandler allow
creation of a group writable log file?
- ScraperWikiX
- create the log directory with the right permissions:
mkdir /var/log/django/
chown django.django /var/log/django/
chmod 2775 /var/log/django/ (chmod
g+ws /var/log/django/ )
- create
local_logging package
- manually:
cd myproject
mkdir local_logging
touch local_logging/__init__.py
- Eclipse:
- (right-click over my_project) New
-> PyDevPackage: local_logging
- my_project/local_logging/custom_handlers.py
import
logging.handlers
import os
class GroupWriteRotatingFileHandler(logging.handlers.RotatingFileHandler):
"""
Create a rotating log
file with group write permissions
(so as it can be
modified by regular user (who calls
manage.py) and user that executes celery
or uwsgi)
"""
def _open(self):
prevumask=os.umask(0o002)
rtv=logging.handlers.RotatingFileHandler._open(self)
os.umask(prevumask)
return rtv
- my_project/settings.py
import logging
from local_logging import custom_handlers
logging.custom_handlers = custom_handlers
'logfile': {
...
'class':'logging.custom_handlers.GroupWriteRotatingFileHandler',
'filename':
"/var/log/django/myproject.log",
'maxBytes':
50000,
'backupCount':
20,
...
- Simple Log to File example for
django 1.3+
|
Activitat i notificacions / Activity and
notifications
|
- Info
- Paquets / Packages
- Django packages:
-
- django-activity-stream
- Implements Activity
Streams
- "An action is a description of an action that was
performed (Verb) at some instant in time by some Actor
on some optional Target that results in an Action Object
getting created/updated/deleted."
-
component
|
|
example
|
Actor
|
subjecte
|
Geraldine...
|
Verb
|
verb
|
...posted...
|
Action object
|
objecte directe
|
...a photo...
|
Target
|
objecte indirecte
|
...to
her album.
|
- Instal·lació / Installation
pip install django-activity-stream
- Opcional / Optional
- my_project/settings.py
INSTALLED_APPS = (
...
'actstream',
)
# django-activity-stream
ACTSTREAM_SETTINGS = {
'FETCH_RELATIONS': True,
'USE_JSONFIELD': True,
}
- my_project/urls.py
# allauth (needed by
django-activity-stream)
url(r'^accounts/',
include('allauth.urls')),
# django-activity-stream
url(r'^activity/',
include('actstream.urls')),
my_app/__init__.py
default_app_config =
'my_app.apps.MyAppConfig'
- Model
registration
- "In order to have your models be either an actor,
target, or action object they must first be
registered with actstream."
- my_app/apps.py
from django.apps
import AppConfig
class MyAppConfig(AppConfig):
name = 'my_app'
def ready(self):
#
includes must be here to avoid error messages
about app not loaded yet
from django.contrib.auth.models import User
from actstream import registry
registry.register(self.get_model('MyModel'))
registry.register(User)
- my_app/xxx.py
from actstream import
action
# activity (action_object must be a registered
object type)
action.send(user,
verb='CONFIRMED', action_object=self)
- Problemes / Problems
- django.core.exceptions.AppRegistryNotReady: Apps
aren't loaded yet.
- Usuaris anònims com a actors / Anonymous users as
actors
- django-notifications
- derivat de django-activity-stream,
amb aquests camps addicionals / derived from django-activity-stream, with these additional fields:
level ("success", "info", "warning",
"error")
recipient (User)
unread
deleted
emailed
- NotificationQuerySet
unread()
read()
mark_all_as_read()
mark_all_as_unread()
deleted()
active()
mark_all_as_deleted()
mark_all_as_active()
- Instal·lació / Installation
pip install django-notifications-hq
django-model-utils==2.3.1
- Problemes / Problems
AttributeError: 'module' object has no
attribute 'PassThroughManager'
- my_project/settings.py
INSTALLED_APPS = (
...
'notifications',
)
- my_project/urls.py
import notifications
...
url('^inbox/notifications/',
include(notifications.urls)),
- my_app/xxx.py
from notification import
notify
# create a notification with
my_instance as the actor
notify.send(my_instance,
recipient=..., action_object=..., verb=...)
- Problemes / Problems
- FieldError:
Field 'action_object' does not generate an automatic
reverse relation and therefore cannot be used for
reverse querying. If it is a GenericForeignKey,
consider adding a GenericRelation.
- JSON reencoding
in data field
- quan una classe hereta de Notification,
apareix el problema de recodificació recursiva,
però només en CentOS. En Mageia ho fa bé.
L'única diferència és que a Mageia, el camp
notifications.notification.data s'ha creat de
tipus jsonb, i en CentOS s'ha creat de tipus
text. Per què?
- psycopg
- django-jsonfield
(Github) (
pip install jsonfield )
(used by django-notifications-hq)
- django-jsonfield
(Bitbucket) (
pip install django-jsonfield )
(not used by django-notifications-hq)
- django-notifications
- django
|
Correu electrònic / Email
|
- Sending
email
-
- Steps
- Install one email
server:
- Postfix
python -m smtpd -n -c DebuggingServer
localhost:1025
- settings.py (defaults specified in
lib/python2.7/site-packages/django/conf/global_settings.py )
EMAIL_BACKEND =
'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'localhost'
EMAIL_PORT = 25
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False
EMAIL_USE_SSL = False
EMAIL_SSL_CERTFILE = None
EMAIL_SSL_KEYFILE = None
EMAIL_TIMEOUT = None
DEFAULT_FROM_EMAIL = 'Whatever
<whatever@example.com>'
# for debugging purposes:
EMAIL_BACKEND='django.core.mail.backends.console.EmailBackend'
EMAIL_BACKEND =
'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.example.org'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'my_smtp_user'
EMAIL_HOST_PASSWORD = 'my_smtp_password'
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = 'Whatever
<whatever@example.org>'
- Test
- Testing tools
- settings.py
# from address used in
mail_admins() and error reporting for production
SERVER_EMAIL='user@example.org'
- ./manage.py shell
from django.core.mail import mail_admins
mail_admins('hello', 'hello world')
- SMTP services
- Verificació d'una adreça de correu electrònic / Email address
verification
|
Push notifications
|
- Push notifications
- APNS (Apple)
- Apple:
Root
Certificate Authority
- Technical
Note
TN2265: Troubleshooting push notifications
- Generate
Apple
Push Notification Certificate
- Apple
Push
Notification Services in iOS 6 Tutorial: Part 1/2
- SSL error while
implementing Apple Push Notification
- bulk
- Easy APNS (php)
- Concateneu el certificat
de client i la clau privada en un sol fitxer /
Concatenate client certificate and private key:
- one of the following:
- extract the certificate from a pkcs12 file:
openssl
pkcs12 -clcerts -nokeys -out cert.pem
-in cert.p12
- convert DER
certificate to PEM:
openssl
x509 -in cert.cer -inform DER -out
cert.pem -outform PEM
- extract the private key from a pkcs12 file:
openssl
pkcs12 -nocerts -nodes -out key.pem -in
key.p12
- concatenate the certificate and the private key
into a single PEM file:
cat
cert.pem key.pem > ck.pem
- Comproveu que funciona /
Check that is working:
wget
https://www.entrust.net/downloads/binary/entrust_2048_ca.cer
openssl s_client -connect
gateway.sandbox.push.apple.com:2195 -cert
ck_development.pem -debug -showcerts -CAfile
entrust_2048_ca.cer openssl s_client
-connect gateway.push.apple.com:2195 -cert
ck_production.pem -debug -showcerts -CAfile
entrust_2048_ca.cer
- you should be able to type some characters and
then the connection will be closed
- Problemes / Problems
- Comproveu que el token que ha generat l'aplicació
correspon al mode de funcionament del servidor (*):
- si el token generat a l'aplicació és de desenvolupament,
el servidor haurà de contactar amb
gateway.sandbox.push.apple.com:2195 fent servir
el certificat de desenvolupament
- si el token generat a l'aplicació és de producció,
el servidor haurà de contactar amb
gateway.push.apple.com:2195 fent servir el
certificat de producció
- El
missatge no es rep
- FCM - Firebase Cloud
Messaging (Google)
- GCM - Google Cloud
Messaging (replaced by FCM)
- Libraries
- django-push-notifications
(Adys
fork)
- APNS,
FCM/GCM, WNS
- DRF
- installation
- using pip
pip install django-push-notifications
- from source
pip install django-uuidfield
cd ~/src/
git clone
https://github.com/Adys/django-push-notifications.git
cd django-push-notifications
su; python setup.py install
- settings.py
INSTALLED_APPS = (
...
"push_notifications"
)
PUSH_NOTIFICATIONS_SETTINGS = {
"FCM_API_KEY": "[your api key]",
"GCM_API_KEY": "[your api key]",
"APNS_CERTIFICATE": "/path/to/your/certificate.pem",
"APNS_TOPIC": "com.example.myapp",
"WNS_PACKAGE_SECURITY_ID": "[your package
security id, e.g: 'ms-app://e-3-4-6234...']",
"WNS_SECRET_KEY": "[your app secret key, e.g.:
'KDiejnLKDUWodsjmewuSZkk']",
}
- Problemes / Problems
- El missatge no es rep perquè té caràcters
especials / Message is not received because it
contains special characters:
- Solució / Solution
- apns.py
data = json.dumps({"aps": data},
separators=(",", ":"),
ensure_ascii=True)
- El servidor retorna "Error: NotRegistered"
- django-iphone-push
- PyAPNs
|
|
- Slack setup
- Bots
(deprecated)
- Formatting
text for app surfaces
- Slack apps
- Basic
app setup
- create a new app
- add scopes:
- (sidebar) OAuth & Permissions -> Scopes
-> Bot Token Scopes -> Add an OAuth Scope
- install the app:
- (sidebar) Install app -> Install to
workspace
- Bot User OAuth Token will be shown (also
available from OAuth & Permissions ->
OAuth Tokens for Your Workspace)
- from Slack, invite application to the channel
/invite -> Add applications to
this channel
- use curl:
- list of conversations
curl
https://slack.com/api/conversations.list
-H "Authorization: Bearer xoxb-12..."
>conversations.json
- jq
-r '.channels[] | [.id, .name] | join("
")' conversations.json | sort -k 2
- ...
- ...
- Paquets /
Packages
- Ús
|
Tasques / Tasks
|
- Using channels
(websockets)
- Celery
(asynchronous processing)
- First
steps with Celery
- Índex / Index
- Using
Celery
with Django (example)
- General
(project-wide) settings
- myproject/myproject/celery.py
from __future__ import
absolute_import
import os
from celery import Celery
from django.conf import settings
# set the default Django settings module for
the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'myproject.settings')
app = Celery('myproject')
# Using a string here means the worker will
not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda:
settings.INSTALLED_APPS)
@app.task(bind=True)
def debug_task(self):
print('Request:
{0!r}'.format(self.request))
- myproject/myproject/__init__.py
from __future__ import
absolute_import
# This will make sure the app is always
imported when
# Django starts so that shared_task will use
this app.
from .celery import app as celery_app
- myproject/myproject/settings.py
# celery
BROKER_URL = 'amqp://myuser:mypassword@localhost/ myvhost '
django-celery
- Deprecated
since celery 3.1
- Use it only if you want to:
- save task results to the database
- save schedules (periodic tasks) to the
database (and manage them from the admin)
- Using
the
Django ORM/Cache as a result backend
pip install django-celery
- myproject/myproject/settings.py
INSTALLED_APPS = (
...
'djcelery',
)
python manage.py migrate djcelery /
python manage.py syncdb
- myproject/myproject/celery.py
(use settings.py instead)
# djcelery
app.conf.update(
CELERY_RESULT_BACKEND='djcelery.backends.database:DatabaseBackend',
)
- myproject/settings.py
## djcelery
# store task results in the database
CELERY_RESULT_BACKEND='djcelery.backends.database:DatabaseBackend'
- admin.py
- Tasques
periòdiques amb django-celery / Periodic tasks
with django-celery
- Unregister Djcelery from Admin (optional):
- How can I disable
the Django Celery admin modules?
- admin.py
# unregister
Djcelery
from djcelery.models import (TaskState,
WorkerState,
PeriodicTask,
IntervalSchedule, CrontabSchedule)
admin.site.unregister(TaskState)
admin.site.unregister(WorkerState)
admin.site.unregister(IntervalSchedule)
admin.site.unregister(CrontabSchedule)
admin.site.unregister(PeriodicTask)
- Run the worker
process
- command
line (for development)
- worker
celery -A myproject worker -l info
- worker + scheduler (periodic
task) (beat)
celery -A myproject worker -B
-l info
- daemon
(for production)
- Running
the
worker as a daemon
- Daemonization
-
- Example (including celerybeat)
- CentOS
- Examples
- Troubleshooting
- systemd issues
- celery issues
- Celery
Daemon does not work on Centos 7
- executables (ExecStart*) specified
in celery.service must be specified
directly, not as variables from
EnvironmentFile
- user and group (User, Group)
specified in celery.service must be
specified directly, not as variables
from EnvironmentFile
- when using virtualenv, executable
is (instead of just "celery", or
"/home/centos/myproject/env/bin/celery"):
- /home/centos/myproject/env/bin/python
-m celery
- user and group specified in
celery.service (User=django,
Group=django) must have access to:
- /var/{log,run}/celery/...
- included in
celery[beat].service:
mkdir -p
/var/log/celery; chown
django.django
/var/log/celery
mkdir -p
/var/run/celery; chown
django.django
/var/run/celery
- (?) celery.conf
- d /var/run/celery 0755
celery celery -
d /var/log/celery 0755
celery celery -
- WorkingDirectory=/home/centos/myproject/
- only if Django logger
specify a log file inside
this directory
- but my recommendation for
log file is:
/var/log/django/myproject.log
- check
- manually start and stop of
worker(s):
su django
/home/centos/myproject/env/bin/python
-m celery worker -A
myproject
--workdir=/home/centos/myproject
- should show console
output (in yellow) with
prints when calling task
functions (tasks.py)
from another console:
cd /home/centos/myproject/
source
env/bin/activate
./manage.py
shell
from
myproject.tasks
import *
do_something.delay()
- if no output is shown
at first console:
- check rabbitmq
queues for
virtual host
celery_vhost:
$
./rabbitmqadmin
-V
celery_vhost
-u celery_user
-p
<password>
list queues
#
rabbitmbctl -p
celery_vhost
list_queues
#
rabbitmqctl -n
rabbit@ip-172-31-20-115
-p
celery_vhost
list_queues
- if celery
queue is not
empty, purge it:
$
./rabbitmqadmin
-V
celery_vhost
-u celery_user
-p
<password>
purge queue
name=celery
/home/centos/myproject/env/bin/python
-m celery multi start w1 --verbose
-A myproject
--workdir=/home/centos/myproject
--pidfile=/var/run/celery/%N.pid
--logfile=/var/log/celery/%N.log
--loglevel="DEBUG"
ls -l /var/run/celery/w1.pid
- should show a
valid file (with a
pid number inside)
tail -f /var/log/celery/w1.log
- should show prints
from called
functions (tasks.py)
/home/centos/myproject/env/bin/python
-m celery multi stopwait
w1 --verbose
--pidfile=/var/run/celery/%N.pid
- should show:
celery multi
v3.1.19 (Cipater)
> Stopping
nodes...
> w1@...: TERM
-> 15263
> Waiting for 1
node ->
15263.....
> w1@...: OK
- start service
systemctl start
celery.service
systemctl status
celery.service
- should show:
tail -f /var/log/celery/w1.log
- should show prints
from called
functions (tasks.py)
- if status is showing:
- Active: inactive
(dead)
- try again to start
journalctl
-u celery
more
/var/run/celery/*
more
/var/log/celery/*
- rabbitmq
is running and permissions are
ok (BROKER_URL)
- celery.conf
(/etc/sysconfig/celery)
# this
file must be installed as
/etc/sysconfig/celery
## common worker and beat settings
# Absolute or relative path to the
'celery' command:
#CELERY_BIN="/home/centos/my_project/env/bin/python
-m celery"
# App instance to use
# comment out this line if you
don't use an app.
# directory name where celery.py
resides
CELERY_APP="my_app"
# Workers should run as an
unprivileged user.
# You need to create
this user manually (or you can
choose
# a user/group
combination that already exists,
e.g. nobody).
#CELERYD_USER="celery"
#CELERYD_GROUP="celery"
## Worker settings
CELERYD_NODES="w1"
CELERYD_OPTS="--time-limit=300
--concurrency=8"
CELERYD_LOG_DIR="/var/log/celery"
CELERYD_LOG_FILE="/var/log/celery/%N.log"
CELERYD_LOG_LEVEL="INFO"
CELERYD_PID_DIR="/var/run/celery"
CELERYD_PID_FILE="/var/run/celery/%N.pid"
CELERYD_CHDIR="/home/centos/my_project/"
## Beat settings
CELERYBEAT_LOG_DIR="/var/log/celery"
CELERYBEAT_LOG_FILE="/var/log/celery/beat.log"
CELERYBEAT_LOG_LEVEL="INFO"
CELERYBEAT_PID_DIR="/var/run/celery"
CELERYBEAT_PID_FILE="/var/run/celery/beat.pid"
CELERYBEAT_SCHEDULE="/var/run/celery/celerybeat-schedule"
CELERYBEAT_CHDIR="/home/centos/my_project/"
DJANGO_SETTINGS_MODULE="myproject.settings"
# AWS boto
credentials
# needed when instance has no IAM
role
# defining them in
myproject.settings is not enough
#AWS_ACCESS_KEY_ID = '...'
#AWS_SECRET_ACCESS_KEY = '...'
- celery.service
(/usr/lib/systemd/system/celery.service)
[Unit]
Description=Celery workers
After=network.target redis.target
[Service]
Type=forking
# same value as
${CELERYD_PID_FILE}, but must be
specified.
# needed in order to avoid some
immediate stops of the service
PIDFile= /var/run/celery/w1.pid
# User and Group cannot be
specified at EnvironmentFile
User=django
Group=django
EnvironmentFile=-/etc/sysconfig/celery
# run ExecStartPre as priviledged
user and set up /var/run
PermissionsStartOnly=true
#ExecStartPre=/usr/sbin/useradd
celery
ExecStartPre=-/usr/bin/mkdir -p
${CELERYD_PID_DIR}
ExecStartPre=/usr/bin/chown -R
django:django ${CELERYD_PID_DIR}
ExecStartPre=-/usr/bin/mkdir -p
${CELERYD_LOG_DIR}
ExecStartPre=/usr/bin/chown -R
django:django ${CELERYD_LOG_DIR}
# executable cannot be specified
at EnvironmentFile
ExecStart=/home/centos/myproject/env/bin/python
-m celery \
--workdir=${CELERYD_CHDIR} \
-A
${CELERY_APP} \
multi
start ${CELERYD_NODES} --verbose
\
--pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} \
--loglevel="${CELERYD_LOG_LEVEL}"
\
${CELERYD_OPTS}
ExecStop=/home/centos/myproject/env/bin/python
-m celery multi stopwait
${CELERYD_NODES} \
--pidfile=${CELERYD_PID_FILE}
ExecReload=/home/centos/myproject/env/bin/python
-m celery multi restart
${CELERYD_NODES} \
-A
${CELERY_APP} \
--workdir=${CELERYD_CHDIR} \
--pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} \
--loglevel="${CELERYD_LOG_LEVEL}"
\
${CELERYD_OPTS}
[Install]
WantedBy=multi-user.target
- celerybeat.service
(/usr/lib/systemd/system/celerybeat.service)
[Unit]
Description=Celery beat scheduler
After=network.target redis.target
[Service]
Type=simple
User=django
Group=django
EnvironmentFile=-/etc/sysconfig/celery
#WorkingDirectory=$CELERYD_CHDIR
# run ExecStartPre as priviledged
user and set up /var/run
PermissionsStartOnly=true
#ExecStartPre=/usr/sbin/useradd
celery
ExecStartPre=-/usr/bin/mkdir -p
${CELERYBEAT_PID_DIR}
ExecStartPre=/usr/bin/chown -R
django:django
${CELERYBEAT_PID_DIR}
ExecStartPre=-/usr/bin/mkdir -p
${CELERYBEAT_LOG_DIR}
ExecStartPre=/usr/bin/chown -R
django:django
${CELERYBEAT_LOG_DIR}
ExecStart=/home/centos/myproject/env/bin/python
-m celery beat \
-A
${CELERY_APP} \
--workdir=${CELERYBEAT_CHDIR} \
--pidfile=${CELERYBEAT_PID_FILE} \
--logfile=${CELERYBEAT_LOG_FILE} \
--loglevel=${CELERYBEAT_LOG_LEVEL}
\
--schedule=${CELERYBEAT_SCHEDULE}
ExecStop=/bin/systemctl kill
celerybeat.service
[Install]
WantedBy=multi-user.target
- celery.logrotate
/var/log/celery/*log
{
daily
rotate 10
size 5M
}
- install_celery.sh
#!/bin/bash
if [[ $EUID -ne 0 ]]; then
echo "This script must be
run as root" 1>&2
exit 1
fi
# create user celery if it does
not exist
if ! id -u django > /dev/null
2>&1; then
useradd django
echo "Created
user django"
else
echo "User
django already exists"
fi
echo "Copying files..."
install celery.conf
/etc/sysconfig/celery
install celerybeat.service
/usr/lib/systemd/system/celerybeat.service
install celery.service
/usr/lib/systemd/system/celery.service
- fabfile.py
(alternative to install_celery.sh)
def
celery_setup():
"""
Setup Celery
(worker)
"""
print
'-------------- celery_setup
--------------'
put('celery.conf',
'/etc/sysconfig/celery',
use_sudo=True)
put('celery.service',
'/usr/lib/systemd/system/celery.service',
use_sudo=True)
put('celery.logrotate',
'/etc/logrotate.d/celery',
mode=int('644', 8), use_sudo=True)
env.sudo('chown
root.root
/etc/logrotate.d/celery')
env.sudo('systemctl enable
celery.service')
env.sudo('systemctl start
celery.service')
def celerybeat_setup():
"""
Setup Celery
(scheduler/beat)
"""
print
'-------------- celerybeat_setup
--------------'
put('celerybeat.service',
'/usr/lib/systemd/system/celerybeat.service',
use_sudo=True)
env.sudo('systemctl enable
celerybeat.service')
env.sudo('systemctl start
celerybeat.service')
- Ubuntu
- celeryd.conf (/etc/default/celeryd)
# Names of
nodes to start
# most will only start
one node:
CELERYD_NODES="worker1"
# but you can also
start multiple and configure
settings
# for each in
CELERYD_OPTS (see `celery multi
--help` for examples).
#CELERYD_NODES="worker1 worker2
worker3"
# Absolute or relative path to the
'celery' command:
#CELERY_BIN="/usr/local/bin/celery"
#CELERY_BIN="/virtualenvs/def/bin/celery"
CELERY_BIN="/opt/p27/bin/celery"
# App instance to use
# comment out this line if you
don't use an app.
# (directory name where celery.py
resides)
CELERY_APP="myproject"
# or fully qualified:
#CELERY_APP="proj.tasks:app"
# Where to chdir at start.
CELERYD_CHDIR="/home/ubuntu/my_project/"
# Extra command-line arguments to
the worker
CELERYD_OPTS="--time-limit=300
--concurrency=8"
# %N will be replaced with the
first part of the nodename.
CELERYD_LOG_FILE="/var/log/celery/%N.log"
CELERYD_PID_FILE="/var/run/celery/%N.pid"
# Workers should run as an
unprivileged user.
# You need to create
this user manually (or you can
choose
# a user/group
combination that already exists,
e.g. nobody).
CELERYD_USER="ubuntu"
CELERYD_GROUP="ubuntu"
# If enabled pid and log
directories will be created if
missing,
# and owned by the userid/group
configured.
CELERY_CREATE_DIRS=1
- celerybeat.conf
(/etc/default/celerybeat)
# Absolute
or relative path to the 'celery'
command:
#CELERY_BIN="/usr/local/bin/celery"
#CELERY_BIN="/virtualenvs/def/bin/celery"
CELERY_BIN="/opt/p27/bin/celery"
# App instance to use
# comment out this line if you
don't use an app
CELERY_APP="my_app"
# or fully qualified:
#CELERY_APP="proj.tasks:app"
# Where to chdir at start.
CELERYBEAT_CHDIR="/home/user/my_project/"
# Extra arguments to celerybeat
#CELERYBEAT_OPTS="--schedule=/var/run/celery/celerybeat-schedule"
CELERYD_USER="ubuntu"
CELERYD_GROUP="ubuntu"
# Django
export
DJANGO_SETTINGS_MODULE="my_app.settings"
CELERYD_CHDIR="/home/user/my_project/"
- wget
https://raw.githubusercontent.com/celery/celery/3.1/extra/generic-init.d/celerybeat;
chmod+x celerybeat
- wget
https://raw.githubusercontent.com/celery/celery/3.1/extra/generic-init.d/celeryd;
chmod+x celeryd
- install_celery.sh
# worker
install celeryd.conf
/etc/default/celeryd
install celeryd /etc/init.d/
update-rc.d celeryd defaults
# scheduler (a worker is also
needed)
install celerybeat.conf
/etc/default/celerybeat
install celerybeat /etc/init.d/
update-rc.d celerybeat defaults
sudo ./install_celery.sh
sudo service celeryd start
sudo service celerybeat start
- Monitoratge /
Monitoring
- Events
- Real-time
processing
- celery_events.py
from celery import
Celery
def my_monitor(app):
def
announce_received_tasks(event):
...
def
announce_started_tasks(event):
...
def
announce_failed_tasks(event):
...
def
announce_succeeded_tasks(event):
...
def
announce_rejected_tasks(event):
...
def
announce_revoked_tasks(event):
...
with app.connection() as
connection:
recv = app.events.Receiver(
connection,
handlers={
"task-received": announce_received_tasks,
"task-started": announce_started_tasks,
"task-failed": announce_failed_tasks,
"task-succeeded": announce_succeeded_tasks,
"task-rejected": announce_rejected_tasks,
"task-revoked": announce_revoked_tasks,
"*": state.event,
},
)
recv.capture(limit=None, timeout=None,
wakeup=True)
def main(args):
broker =
"amqp://my_user:my_password@localhost/my_host"
app = Celery(broker=broker)
my_monitor(app)
if __name__ == "__main__":
main(sys.argv[1:])
- test_celery_events.py (WIP)
from django.test
import TestCase
from mock import patch, Mock
from celery_events import main
class CeleryEventsTestCase(TestCase):
#
@patch("celery_events.Celery.events.Receiver._receive")
@patch("celery_events.Celery.connection")
def test_main(self,
mock_connection):
#
https://github.com/celery/celery/blob/master/t/unit/events/test_events.py
mock_connection = Mock()
mock_connection.transport_cls = "memory"
#mock_receive = Mock()
args = []
main(args)
- Ús / Usage
- Puntual /
Single
- Option 1: from views.py
- myproject/my_app/tasks.py
from celery
import task
@task
def do_something(parameter)
...
- myproject/my_app/views.py
from
my_app.tasks import do_something
...
resultat_asinc = do_something.delay(parameter_value)
- manually test (no Celery worker is needed;
Celery is only needed when executing
functions with .delay())
source env/bin/activate
./manage shell
from my_app.tasks import *
do_something()
- or, directly from a single command
- Option
2: from models.py
- Example 1: image
conversion, all local (consider django-stdimage)
- myproject/my_app/models.py
from
celery import task
import subprocess
class Imatge(models.Model):
nom =
models.CharField(max_length=100)
user =
models.ForeignKey( UploadUser )
media =
models.ImageField(upload_to='ima')
converted_media
=
models.ImageField(upload_to='conv',
blank=True)
def save(self,
*args, **kwargs):
# optional parameter to indicate
whether perform
# conversion or not. Defaults to
True
do_conversion =
kwargs.pop('do_conversion', True)
# do something only when the entry
is created
if not self.pk:
super(Imatge,
self).save(*args, **kwargs)
# do something every time the
entry is updated
if do_conversion:
image_convertida
= convert_imatge.delay(self.pk)
print "media.name: %s" %
self.media.name
print "media.file.name: %s" %
self.media.file.name
print "converted upload_to: %s" %
self.converted_media.field.upload_to
# then call the parent save:
super(Imatge, self).save(*args,
**kwargs)
def
__unicode__(self):
return "%s (%s)" % (self.nom,
self.media)
# no es pot posar a tasks.py
perque caldria un import del
# model Imatge, i faria un bucle
infinit
@task
def convert_imatge(imatge_id):
imatge =
Imatge.objects.get(pk=imatge_id)
print
"convert_imatge: pk=%s" %
imatge.id
# input image
abs_input_image
= imatge.media.file.name
# get the
upload_to from model definition
upload_path =
imatge.converted_media.field.upload_to
abs_upload_path
= os.path.join(
settings.MEDIA_ROOT, upload_path )
# create it if
it does not exist, as it is not
automatically created
if not
os.access(abs_upload_path,
os.F_OK):
print "creating %s" %
abs_upload_path
os.mkdir(abs_upload_path)
else:
print "dir %s already exists" %
abs_upload_path
# output image
output_image =
os.path.join(upload_path,
'toto.png')
abs_output_image = os.path.join(
settings.MEDIA_ROOT, output_image
)
# command to
convert (ImageMagick)
convert_command
= "convert %s %s" %
(abs_input_image,
abs_output_image)
print
convert_command
try:
# shell=True es per a poder posar
tota l'ordre de cop
convert_result = subprocess.call(convert_command,
shell=True)
print "Result: %s" %
convert_result
except
Exception as e:
convert_result = None
print "Failed to convert image %s
to %s" % (abs_input_image,
abs_output_image)
print "Error: %s" % e
# update
the name of the generated image
imatge.converted_media.name =
output_image
# do not
perform conversion, to avoid
infinite loop
imatge.save(do_conversion=False)
print
"converted image width: %s" %
imatge.converted_media.width
print
"converted image height: %s" %
imatge.converted_media.height
- Example 2: image conversion, input image
uploaded to AWS
S3, output image uploaded to AWS S3 (via a
temporary local file):
- myproject/my_app/models.py
from
celery import task
import subprocess class
Imatge(models.Model):
nom =
models.CharField(max_length=100)
user =
models.ForeignKey( UploadUser )
media =
models.ImageField(upload_to='ima')
converted_media
=
models.ImageField(upload_to='conv',
blank=True)
def save(self,
*args, **kwargs):
# optional parameter to indicate
whether perform
# conversion or not. Defaults to
True
do_conversion =
kwargs.pop('do_conversion', True)
# do something only when the entry
is created
if not self.pk:
super(Imatge,
self).save(*args, **kwargs)
# do something every time the
entry is updated
if do_conversion:
convert_imatge_s3.delay(self.pk)
# then call the parent save:
super(Imatge,
self).save(force_update = True)
def
__unicode__(self):
return "%s (%s)" % (self.nom,
self.media)
@task
def convert_imatge_s3(imatge_id):
from
django.core.files.uploadedfile
import SimpleUploadedFile
import tempfile
imatge =
Imatge.objects.get(pk=imatge_id)
print
"convert_imatge_s3: pk=%s" %
imatge.id
# input image
url_input_image
= imatge.media.url
# output image
OUTPUT_IMAGE_EXT = 'png'
OUTPUT_IMAGE_CONTENT_TYPE =
'image/png'
f_out =
tempfile.NamedTemporaryFile(suffix=".%s"%OUTPUT_IMAGE_EXT,
delete=False)
tmp_output_image = f_out.name
# command to
convert
convert_command
= "convert '%s' '%s'" %
(url_input_image,
tmp_output_image)
print
convert_command
try:
# shell=True es per a poder posar
tota l'ordre de cop
convert_result = subprocess.call(convert_command,
shell=True)
print "Result: %s" %
convert_result
except
Exception as e:
convert_result = None
print "Failed command: %s" %
(convert_command)
print "Error: %s" % e
# prepare
an object with the generated
temporary image
# (? why the
name of the original image:
imatge.media.name?)
suf =
SimpleUploadedFile(
os.path.split(imatge.media.name)[-1],
f_out.read(),
content_type=OUTPUT_IMAGE_CONTENT_TYPE
)
# upload
converted image to S3 and set the
name.
# save set to
False to avoid infinite loop
imatge.converted_media.save(
'%s_convertida.%s'
% (os.path.splitext(suf.name)[0],
OUTPUT_IMAGE_EXT),
suf,
save=False
)
# delete
temporary output file
os.remove(tmp_output_image)
# save the
parent object because this is an
async process.
# do not
perform conversion, to avoid
infinite loop
imatge.save(do_conversion=False)
- myproject/my_app/s3utils.py
from
storages.backends.s3boto import
S3BotoStorage
from django.utils.functional
import SimpleLazyObject
from django.utils.deconstruct
import deconstructible
from django.conf import settings
MediaRootS3BotoStorage = lambda:
S3BotoStorage(location=settings.S3_MEDIA_PATH)
@deconstructible
class
NewS3BotoStorage(S3BotoStorage):
"""
Deconstructible
subclass to avoid Django 1.7
error:
ValueError:
Cannot serialize:
<storages.backends.s3boto.S3BotoStorage
object...
"""
pass
- myproject/myproject/settings.py
# media:
uploaded files
DEFAULT_FILE_STORAGE =
'my_app.s3utils.MediaRootS3BotoStorage'
S3_MEDIA_PATH = "media"
MEDIA_ROOT = '/%s/' %
S3_MEDIA_PATH
MEDIA_URL =
"https://%s.s3.amazonaws.com/" %
AWS_STORAGE_BUCKET_NAME
- Puntual, en un moment determinat / Execute once,
at given time
- Periodic
tasks
- In celery 3.1,
making django periodic task
- option 1: schedules from admin (django-celery)
- Dinamically add/remove tasks
- myproject/settings.py
#
celery
BROKER_URL =
'amqp://myuser:mypassword@localhost//'
# store
schedules in the database
# (will include schedules defined
in CELERYBEAT_SCHEDULE
# every time the celery beat is
restarted, and entries modified
# from admi will be overwritten) CELERYBEAT_SCHEDULER
=
'djcelery.schedulers.DatabaseScheduler'
- option 2: fixed schedules
- myproject/settings.py
# needed by " from
celery.schedules import crontab "
from __future__ import
absolute_import
...
# celery
BROKER_URL =
'amqp://myuser:mypassword@localhost//'
from datetime import timedelta
from celery.schedules import crontab
# if CELERYBEAT_SCHEDULER
=
'djcelery.schedulers.DatabaseScheduler',
# these entries will be included in
the database, every time
the celery beat is restarted
CELERYBEAT_SCHEDULE = {
'do_something-every-10-seconds': {
'task': 'myproject.tasks.do_something',
'schedule':
timedelta(seconds=10),
'args': (16, 16)
},
'do_something_else-at_2:00_every_day':
{
'task': 'myproject.tasks.do_something_else',
'schedule':
crontab(hour='2',minute='0'),
},
}
- myproject/__init__.py
from __future__
import absolute_import
# This will make sure the app is always
imported when
# Django starts so that shared_task will
use this app.
from myproject.celery import app as
celery_app
- myproject/celery.py
from __future__
import absolute_import
import os
from celery import Celery
from django.conf import settings
# set the default Django settings module
for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'myproject.settings')
app = Celery('myproject')
# Using a string here means the worker
will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda:
settings.INSTALLED_APPS)
# add the following line to have access to
myproject/tasks.py
app.autodiscover_tasks(lambda:
('myproject',))
@app.task(bind=True)
def debug_task(self):
print('Request:
{0!r}'.format(self.request))
- myproject/tasks.py
from __future__
import absolute_import
from celery import shared_task
@shared_task
def do_something(parameter1,
parameter2):
# print and logger
(console) outputs will be written to log
file specified by celery.service (not
logfile specified by celerybeat.service)
print "[do_something]
parameter1=%d parameter2=%d" %
(parameter1,parameter2)
total = parameter1 +
parameter2
return "total: %d" %
total
@shared_task
def do_something_else():
# print and logger
(console) outputs will be written to log
file specified by celery.service (not
logfile specified by celerybeat.service)
print
"[do_something_else]"
return True
celery
-A myproject beat -l info
celery -A myproject worker -B -l info
|
Cron
|
- Celery periodic
tasks
- django-cron
(Tivix) (github)
- Installation (one of the following):
pip install django_cron
pip install -e
git://github.com/Tivix/django-cron.git#egg=django_cron
- From Django 1.7:
python manage.py makemigrations django_cron
python manage.py migrate
- settings.py
INSTALLED_APPS = [
#...
"django_cron",
]
- my_app/cron.py
from django.utils.translation
import ugettext_lazy as _
from my_app.all_functions import toto_function
from django_cron import CronJobBase, Schedule
import logging
log = logging.getLogger(__name__)
class TotoCronJob(CronJobBase):
RUN_AT_TIMES = ['10:00',]
schedule =
Schedule(run_at_times=RUN_AT_TIMES)
code =
'my_app.toto_cron_job' # a unique
code
def do(self):
log.info("Cron to make toto")
result =
toto_function()
message =
_("Result of toto_function: ") + ", ".join(result)
return
message
from django.utils.translation
import ugettext_lazy as _
from my_app.all_functions import toto_function
from django_cron import CronJobBase, Schedule
import logging
log = logging.getLogger(__name__)
class TotoCronJob(CronJobBase):
RUN_EVERY_MINS = 120 # every 2
hours
schedule =
Schedule(run_every_mins=RUN_EVERY_MINS)
code =
'my_app.toto_cron_job' # a unique
code
def do(self):
log.info("Cron to make toto")
result =
toto_function()
message =
_("Result of toto_function: ") + ", ".join(result)
return
message
- settings.py
CRON_CLASSES = [
"my_app.cron. TotoCronJob",
]
- executa / run
- una vegada / once:
python manage.py runcrons
- programat / scheduled:
- install_crontab.sh
#! /bin/bash
linia_cron="0 * * * * source
/opt/PYTHON27/bin/activate; python
/var/www/toto_web/manage.py runcrons >>
/tmp/cron.log 2>&1; deactivate"
crontab
-l | { cat; echo "${linia_cron}"; } | crontab
-
- django-cron
(code.google)
|
Serialització / Serialization
|
|
Importació i exportació / Import and
export
|
-
- Admin extensions
- General
- django-rest-framework-csv
- Ordered CSVRenderer
- this implementation is needed in order to keep fields
order as specified (in serializer or in data itself),
when using
@ detail_route in ViewSet
(otherwise, the suggested methods in Ordered
Fields are valid)
- renderers.py
from
rest_framework_csv.renderers import CSVRenderer
from collections import OrderedDict
class OrderedCSVRenderer (CSVRenderer):
def render(self, data,
media_type=None, renderer_context={},
writer_opts=None):
if not
isinstance(data, list):
data = [data]
#
if a serializer is used (serizlizer determines the
order), we just take the header from the first
element:
if data [0]:
renderer_context['header'] = data [0].keys()
#
if a serializer is not used (data determines the
order), we need to get an ordered version of all
fields:
# total_od =
OrderedDict()
# for
a in data:
#
total_od.update(a)
# renderer_context['header']
= total_od.keys()
return
CSVRenderer.render(self, data,
media_type=media_type,
renderer_context=renderer_context,
writer_opts=writer_opts)
- serializers.py
class MyMainserializer(...
class MyDetailSerializer(serializers.Serializer):
# this order will be honoured
by JsonRendered, OrderedCSVRenderer
field_2 =
serializers.CharField()
field_1 =
serializers.CharField()
field_3 =
serializers.CharField()
- views.py (using a serializer)
from my_app.renderers
import OrderedCSVRenderer
from my_app.serializers import MyMainSerializer,
MyDetailSerializer
class MyViewSet(viewsets.ModelViewSet):
queryset =
MyModel.objects.all()
serializer_class =
MyMainSerializer
@detail_route( serializer_class
= MyDetailSerializer, renderer_classes =
(JSONRenderer, OrderedCSVRenderer,) )
def my_detail(self, request,
*args, **kwargs):
obj_list = []
# build
temporary object
obj_list = [{"field_1": "value_11",
"field_2":
"value_12",
"field_3":
"value_13",
},
{"field_1":
"value_21",
"field_2":
"value_22",
"field_3":
"value_23",
},
]
serializer = self.get_serializer(obj_list,
many=True)
return
Response(serializer.data)
- views.py (not using a serializer)
class
MyViewSet(viewsets.ModelViewSet):
queryset =
MyModel.objects.all()
serializer_class =
MyMainSerializer
@detail_route( renderer_classes
= (JSONRenderer, OrderedCSVRenderer,) )
def my_detail(self, request,
*args, **kwargs):
obj_list = []
# build
temporary object
obj1 =
OrderedDict()
obj1["field_1"] = "value_11"
obj1["field_2"] = "value_12"
obj1["field_4"] = "value_14"
obj2 =
OrderedDict()
obj2["field_3"] = "value_23"
obj2["field_1"] = "value_21"
obj2["field_2"] = "value_22"
obj_list = [obj1,obj2]
return
Response(obj_list)
- Hierarchical
parser
- parsers.py
class
HierarchicalCSVParser(BaseParser):
"""
Parses CSV serialized data.
The parser assumes the first
line contains the column names.
"""
media_type = 'text/csv'
def parse(self, stream,
media_type=None, parser_context=None):
parser_context = parser_context or {}
delimiter = parser_context.get('delimiter', ',')
try:
encoding
= parser_context.get('encoding',
settings.DEFAULT_CHARSET)
rows =
unicode_csv_reader(universal_newlines(stream),
delimiter=delimiter, charset=encoding)
data = OrderedRows(next(rows))
for row in rows:
row_data
= dict(zip(data.header, row))
hierarchical_data
= self._csv_convert(row_data)
data.append(hierarchical_data)
return
data
except
Exception as exc:
raise
ParseError('CSV parse error - %s' % str(exc))
def
_csv_convert(self,flat_data):
first_level_keys = {key.split(".")[0] for key in
flat_data.keys()}
if
list(first_level_keys)[0].isdigit():
d = []
else:
d = {}
for
first_level_key in first_level_keys:
# a subset of the dictionary with only the entries
with the
# key: first_level_key.* and non empty value
subset
= {key:value for key, value in flat_data.items()
if key.partition(".")[0]==first_level_key and
len(value)>0}
if len(subset) > 0:
at_deepest
= subset.keys()[0].partition(".")[1]==''
if
at_deepest:
#
end of recursivity
d.update(subset)
else:
#
can go deeper
#
remove the first_level_key
flat_second_level_subset
= {key.partition(".")[2]:value for key, value in
subset.items()}
second_level_subset
= self._csv_convert(flat_second_level_subset)
if
first_level_key.isdigit():
#
add to the list
d.append(second_level_subset)
else:
#
add to the dictionary
d[first_level_key]
= second_level_subset
return
d
- XLSRenderer
- renderers.py
import xlwt
from datetime import datetime, date
class XLSRenderer(CSVRenderer):
media_type =
'application/ms-excel'
format = 'xls'
def render(self, data,
media_type=None, renderer_context=None,
sheetname='First'):
table =
self.tablize(data)
wb =
self.to_workbook(table, sheetname=sheetname)
return
wb
# source:
http://fragmentsofcode.wordpress.com/2009/10/09/xlwt-convenience-methods/
def to_workbook(self,
tabular_data, workbook=None, sheetname=None):
"""
Returns
the Excel workbook (creating a new workbook
if
necessary) with the tabular data written to a
worksheet
with
the name passed in the 'sheetname' parameter (or a
default
value if sheetname is None or empty).
"""
wb =
workbook or xlwt.Workbook(encoding='utf8')
if
len(sheetname)>31:
sheetname
= sheetname[:31]
ws =
wb.add_sheet(sheetname or 'Data')
self.to_worksheet(tabular_data, ws)
return
wb
def to_worksheet(self,
tabular_data, worksheet):
"""
Writes
the tabular data to the worksheet (returns None).
Thanks
to John Machin for the tip on using enumerate().
"""
default_style = xlwt.Style.default_style
datetime_style =
xlwt.easyxf(num_format_str='dd/mm/yyyy hh:mm')
date_style =
xlwt.easyxf(num_format_str='dd/mm/yyyy')
for
row, rowdata in enumerate(tabular_data):
worksheet_row
= worksheet.row(row)
for col, val in enumerate(rowdata):
if
isinstance(val, datetime):
val
= val.replace(tzinfo=None)
style
= datetime_style
elif
isinstance(val, date):
style
= date_style
else:
style
= default_style
worksheet_row.write(col,
val, style=style)
- admin.py
from datetime import
datetime
from django.utils.translation import ugettext_lazy
as _
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
model = MyModel
actions =
['export_xls_from_admin_view']
def
export_xls_from_admin_view(self, request,
queryset):
serializer = MyModelSerializer(queryset,
many=True)
wb =
XLSRenderer().render(serializer.data,
sheetname='MyModel')
now =
datetime.now()
filename = "mymodel_%s.xls" %
(now.strftime('%Y%m%d-%H%M%S'))
#
Django < 1.7
#response =
HttpResponse(mimetype="application/ms-excel")
#
Django 1.7
response =
HttpResponse(content_type="application/ms-excel")
response['Content-Disposition'] = 'attachment;
filename=%s' % filename
wb.save(response)
return
response
export_xls_from_admin_view.short_description =
_("Export selected items to XLS")
- XLSParser
- parsers.py
import xlrd
class
HierarchicalXLSParser(HierarchicalCSVParser):
"""
Parses CSV serialized data.
The parser assumes the first
line contains the column names.
"""
media_type =
'application/vnd.ms-excel'
def parse(self, stream,
media_type=None, parser_context=None):
book =
xlrd.open_workbook(file_contents=stream)
sheet =
book.sheet_by_index(0)
data =
[]
header
= []
try:
for row_index in range(sheet.nrows):
row
= []
for
col_index in range(sheet.ncols):
if
row_index==0:
header.append(sheet.cell(row_index,col_index).value)
else:
row.append(sheet.cell(row_index,col_index).value)
if
row_index!=0:
row_data
= dict(zip(header, row))
hierarchical_data
= self._csv_convert(row_data)
data.append(hierarchical_data)
return
data
except
Exception as exc:
raise
ParseError('HierarchicalXLS parse error - %s' %
str(exc))
- actions.py
def
parse_uploaded_file(uploaded_file):
errors = dict()
if uploaded_file.content_type
== 'application/vnd.ms-excel':
fitxer
= File(uploaded_file)
content
= fitxer.read()
#stream
= BytesIO(content)
data =
HierarchicalXLSParser().parse(content)
fitxer.close()
serializer =
EventSerializer(data=data, many=True)
if serializer.is_valid():
serializer.save()
else:
return
serializer.errors
- django-data-importer
- django-import-export
(github)
- django-adaptors
- Marmir
- Python Excel: xlwt / xlrd
- See also: openpyxl
- Outputting
Excel
with Django
- xlwt
convenience methods
- Exporting
data
to Excel or PDF file in Django
- huDjango
serializers
- save to:
- a local file:
- an http response:
...
# Django < 1.7:
#response =
HttpResponse(mimetype="application/ms-excel")
# Django 1.7: response =
HttpResponse(content_type="application/ms-excel")
response['Content-Disposition'] = 'attachment;
filename=%s' % fname
wb.save(response)
- admin.py (using django-adminplus)
from django.http import
HttpResponse
import xlwt
from datetime import datetime, date
from toto_app.models import Toto1, Toto2
# source: http://fragmentsofcode.wordpress.com/2009/10/09/xlwt-convenience-methods/
def to_workbook(tabular_data, workbook=None,
sheetname=None):
"""
Returns the Excel workbook
(creating a new workbook
if necessary) with the tabular data
written to a worksheet
with the name passed in the
'sheetname' parameter (or a
default value if sheetname is None
or empty).
"""
wb = workbook or
xlwt.Workbook(encoding='utf8')
ws = wb.add_sheet(sheetname or
'Data')
to_worksheet(tabular_data, ws)
return wb
def to_worksheet(tabular_data, worksheet):
"""
Writes the tabular data to the
worksheet (returns None).
Thanks to John Machin for the tip
on using enumerate().
"""
default_style =
xlwt.Style.default_style
datetime_style =
xlwt.easyxf(num_format_str='dd/mm/yyyy hh:mm')
date_style =
xlwt.easyxf(num_format_str='dd/mm/yyyy')
for row, rowdata in
enumerate(tabular_data):
worksheet_row = worksheet.row(row)
for col,
val in enumerate(rowdata):
if isinstance(val, datetime):
val
= val.replace(tzinfo=None)
style
= datetime_style
elif isinstance(val, date):
style
= date_style
else:
style
= default_style
worksheet_row.write(col,
val, style=style)
def xls_to_response(xls, fname):
response =
HttpResponse(mimetype="application/ms-excel")
response['Content-Disposition'] =
'attachment; filename=%s' % fname
xls.save(response)
return response
@admin.site.register_view('export_database',_("Export
Database (xls)"))
def export_database(request):
# toto1 model
table = Toto1.objects.values_list()
headers = [f.name for f in
Toto1._meta.fields]
table = [headers] + list(table)
wb = to_workbook(table,
sheetname=_("Toto 1"))
# toto2 model
table = Toto2.objects.values_list()
headers = [f.name for f in
Toto2._meta.fields]
table = [headers] + list(table)
wb = to_workbook(table,
workbook=wb, sheetname=_("Toto 2"))
# toto3 model
table =
Toto3.objects.values_list('field1','field2')
headers =
['field1_header','field2_header']
table = [headers] + list(table)
wb = to_workbook(table,
workbook=wb, sheetname=_("Toto 3"))
return
xls_to_response(wb,'toto.xls')
|
Gràfiques / Charts
|
|
SMS
|
|
Storage
|
|
Etiquetes / Tags
|
- Etiquetes / Tags
- django-taggit
- Docs
-
settings |
models.py |
serializers.py |
INSTALLED_APPS = (
...
'taggit',
'taggit_serializer',
...
) |
from taggit.managers import TaggableManager
class MyModel(models.Model):
tags = TaggableManager() |
from taggit_serializer.serializers import
TagListSerializerField, TaggitSerializer
# order of inheritance is critical:
TaggitSerializer must go first
class MyModelSerializer(TaggitSerializer,
serializers.ModelSerializer):
...
tags = TagListSerializerField(required=False) |
- Create an object and then add some tags to it (cannot
be done in a single call to objects.create):
myobject =
MyModel.objects.create(...)
tags = ['first','second']
myobject.tags.add(*tags)
- Retrieve all tags in an object:
tags = myobject.tags.all()
- Problemes / Problems
- With djangorestframework
- Jerarquies / Hierarchy
- Etiquetes jeràrquiques / Hierarchical tags
|
Metadades / Metadata
|
|
Push
|
- How to build a push system in
django?
- Django
Push
HTTP Response to users [closed]
- Django
push:
Using Server-Sent Events and WebSocket with Django
- SSE (unidirectional)
- Websockets (bidirectional)
- WebSocket
- uWSGI
websockets
- Django
Channels
- Channels
adopted as an official Django project
-
version
|
channels |
Django |
Python |
1
|
|
|
2.0
|
1.11+
|
3.5+
|
-
|
connection
|
|
settings
|
routing
|
view
|
|
gateway
|
|
scope
(persistent):
path, method, headers...
|
event
|
settings.py |
|
|
|
|
http
|
request
|
-
|
|
urls.py
|
views.py
|
|
WSGI
|
websockets |
x
|
x
|
INSTALLED_APPS =
[
...
'channels',
]
ASGI_APPLICATION =
"myproject.routing.application"
|
Routing:
routing.py
|
Consumers:
consumers.py
|
Channel
layers
|
|
|
|
|
|
application = URLRouter([
url(r"^chat/admin/$",
AdminChatConsumer),
url(r"^chat/$",
PublicChatConsumer),
])
application = ProtocolTypeRouter({
"websocket": URLRouter([
url(r"^chat/admin/$", AdminChatConsumer),
url(r"^chat/$", PublicChatConsumer),
]),
"telegram":
ChattyBotConsumer,
})
application = ProtocolTypeRouter({
"websocket": AuthMiddlewareStack(
URLRouter([
url(r"^front(end)/$",
consumers.AsyncChatConsumer),
])
),
})
|
class
ChatConsumer(WebsocketConsumer):
def connect(self):
self.username = "Anonymous"
self.accept()
self.send(text_data="[Welcome %s!]" %
self.username)
def receive(self, *,
text_data):
if
text_data.startswith("/name"):
self.username = text_data[5:].strip()
self.send(text_data="[set
your username to %s]" % self.username)
else:
self.send(text_data=self.username + ": " +
text_data)
def disconnect(self,
message):
pass
|
|
ASGI
|
|
|
|
|
|
class
PingConsumer(AsyncConsumer):
async def
websocket_connect(self, message):
await
self.send({
"type": "websocket.accept",
})
async def
websocket_receive(self, message):
await
asyncio.sleep(1)
await
self.send({
"type": "websocket.send",
"text": "pong",
})
|
|
|
|
|
|
|
|
class
ChattyBotConsumer(SyncConsumer):
def telegram_message(self,
message):
"""
Simple echo handler for telegram messages in any
chat.
"""
self.send({
"type": "telegram.message",
"text": "You said: %s" % message["text"],
})
|
|
|
-
|
|
|
basic
|
websocket
|
json
websocket
|
http
|
|
group
|
message type
|
SyncConsumer
|
AsyncConsumer
|
WebsocketConsumer
|
AsyncWebsocketConsumer
|
JsonWebsocketConsumer
|
AsyncJsonWebsocketConsumer |
AsyncHttpConsumer
|
ASGI
|
websocket
|
websocket.connect
|
# when receiving
a message of type websocket.connect
def websocket_connect(self, event)
|
# when receiving
a message of type websocket.connect
async def websocket_connect(self, event) |
# when receiving
a message of type websocket.connect
def connect(self)
|
# when receiving
a message of type websocket.connect
async def connect(self) |
|
|
|
websocket.receive
|
# when receiving
a message of type websocket.receive
def websocket_receive(self, event)
|
# when receiving
a message of type websocket.receive
async def websocket_receive(self, event) |
# when receiving
a message of type websocket.receive
def receive(self, text_data=None,
bytes_data=None)
|
# when receiving
a message of type websocket.receive
async def receive(self, text_data=None,
bytes_data=None) |
# when receiving
a message of type websocket.receive
def receive_json(self, content)
|
|
|
websocket.disconnect
|
|
|
# when receiving
a message of type websocket.disconnect
def disconnect(self, close_code)
|
# when receiving
a message of type websocket.disconnect
async def disconnect(self, close_code) |
|
|
|
websocket.accept
|
# send message to
websocket, of type websocket.accept
self.send({"type":"websocket.accept"})
|
# send message to
websocket, of type websocket.accept
await self.send({"type":"websocket.accept"}) |
# send message to
websocket, of type websocket.accept
self.accept()
self.accept("subprotocol")
|
# send message to
websocket, of type websocket.accept
await self.accept()
await self.accept("subprotocol") |
|
|
|
websocket.send
|
# send message to
websocket, of type websocket.send
self.send({"type":"websocket.send","text":...}) |
# send message to
websocket, of type websocket.send
await self.send({"type":"websocket.send",...}) |
# send message to
websocket, of type websocket.send
self.send(text_data=...)
self.send(bytes_data=...)
|
# send message to
websocket, of type websocket.send
await self.send(text_data=...)
await self.send(bytes_data=...) |
# send message to
websocket, of type websocket.send
self.send_json(content=...)
|
|
|
websocket.close
|
|
|
self.close()
self.close(code=...)
|
|
|
|
|
http
|
http....
|
|
|
|
|
|
|
|
custom
(e.g. for layers) |
|
chat.message
|
|
|
def receive...
# send message to room group,
of type chat.message
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{"type": "chat_message", ...}
)
|
async def
receive...
# send message to room group,
of type chat.message
await
self.channel_layer.group_send(
self.room_group_name,
{"type": "chat_message", ...}
)
|
|
|
|
|
|
# when receiving
a message of type chat.message
def chat_message(self, event) |
# when receiving
a message of type chat.message
async def chat_message(self, event) |
|
|
|
layers
|
|
action
|
|
|
|
|
|
|
|
|
|
join group
|
|
|
def connect...
# already set by default:
self.channel_name, self.channel_layer
self.room_name=...
self.room_group_name=...
# join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
|
async def
connect...
# already set by default:
self.channel_name, self.channel_layer
self.room_name=...
self.room_group_name=...
# join room group
await
self.channel_layer.group_add(
self.room_group_name,
self.channel_name
) |
|
|
|
|
|
leave group
|
|
|
def disconnect...
# leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
|
async def
disconnect...
# leave room group
await
self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
|
|
|
|
- Tutorial
- Resum / Summary
|
project |
|
app
|
|
|
|
settings
|
route
|
route
|
view
|
templates
|
http
|
mysite_ws/settings.py
|
mysite_ws/urls.py
from
django.contrib import admin
from django.urls import path
from django.conf.urls import include,
url
urlpatterns = [
url(r'^chat/',
include('chat.urls')),
path('admin/',
admin.site.urls),
]
|
chat/urls.py
from
django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index,
name='index'),
url(r'^(?P<room_name>[^/]+)/$',
views.room,
name='room'),
]
|
chat/views.py
from
django.shortcuts import render
from django.utils.safestring import
mark_safe
import json
def index(request):
return
render(request, 'chat/index.html', {})
def room(request,
room_name):
return
render(request, 'chat/room.html', {
'room_name_json':
mark_safe(json.dumps(room_name))
})
|
chat/templates/chat/index.html
chat/templates/chat/room.html
|
ws
|
mysite_ws/settings.py
INSTALLED_APPS
= [
'channels',
...
]
ASGI_APPLICATION
= "mysite_ws.routing.application"
CHANNEL_LAYERS
= {
'default': {
'BACKEND':
'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
|
mysite_ws/routing.py
from
channels.auth import
AuthMiddlewareStack
from channels.routing import
ProtocolTypeRouter, URLRouter
import chat.routing
application
= ProtocolTypeRouter({
# (http->"django
views" is added by default)
'websocket':
AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
),
})
|
chat/routing.py
from
django.conf.urls import url
from . import consumers
websocket_urlpatterns
= [
url(r'^ws/chat/(?P<room_name>[^/]+)/$',
consumers.ChatConsumer),
]
|
chat/consumers.py
from
channels.generic.websocket import
WebsocketConsumer
import json
# synchronous consumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.accept()
def
disconnect(self, close_code):
pass
def receive(self,
text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
self.send(text_data=json.dumps({
'message': message
}))
from
asgiref.sync import async_to_sync
from channels.generic.websocket import
WebsocketConsumer
import json
# synchronous consumer
class
ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name
=
self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' %
self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def
disconnect(self, close_code):
#
Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message
from WebSocket
def receive(self,
text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
#
Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
#
Receive message from room group
def
chat_message(self, event):
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
- ...
# asynchronous consumer
|
|
- Tutorial
Part 1: Basic setup
- Creating a project
- create a general directory, install
Python3 with virtualenv and install Django
mkdir -p ~/src/mysite_ws
cd ~/src/mysite_ws
virtualenv-3.5 env3
source env3/bin/activate
pip install --upgrade pip
pip install django
pip install -U channels
- create a django project: mysite_ws
django-admin startproject
mysite_ws .
- Creating the chat app
./manage.py startapp chat
- mysite_ws/mysite_ws/settings.py
INSTALLED_APPS
= [
'chat',
...
]
- Add the index view
- chat/templates/chat/index.html
- chat/views.py
- chat/urls.py
- mysite_ws/urls.py
- Integrate the Channels library
- mysite_ws/routing.py
- mysite_ws/settings.py
INSTALLED_APPS
= [
'channels',
...
]
# Channels
ASGI_APPLICATION =
"mysite_ws.routing.application"
- Tutorial
Part 2: Implement a Chat Server
- Add the room view
- chat/templates/chat/room.html
- chat/views.py
- chat/urls.py
- Write your first consumer
- chat/consumers.py
- chat/routing.py
- mysite_ws/routing.py
- Enable a channel layer
- to allow communication between several
consumers
- abstractions
- channel: mailbox where messages can be
sent to
- group: group of related channels
- using Redis
- native
- with Docker
sudo systemctl start
docker.service
docker run -p 6379:6379 -d
redis:2.8
pip install channels_redis
- check
$ python3 manage.py shell
>>> import channels.layers
>>> channel_layer =
channels.layers.get_channel_layer()
>>> from asgiref.sync import
async_to_sync
>>>
async_to_sync(channel_layer.send)('test_channel',
{'type': 'hello'})
>>>
async_to_sync(channel_layer.receive)('test_channel')
{'type': 'hello'}
- Tutorial
Part 3: Rewrite Chat Server as Asynchronous
- Rewrite the consumer to be asynchronous
- ...
- Tutorial
Part 4: Automated Testing
- Testing the views
- Install
- Chrome
- chromedriver
and put it somewhere visible by PATH
environment variable
- selenium
- chat/tests.py
- ...
- Consumers
- Routing
- Database access
- Channel layers
- Sessions
- Authentication
- Security
- OriginValidator
- AllowedHostsOriginValidator
- takes same values as defined in
settings.ALLOWED_HOSTS
- Testing
- Worker
and background tasks
./manage.py runworker ...
- Desplegament /
Deployment
- Redis
- /var/log/messages
Jul 6 11:08:06
ip-172-31-21-155 daphne: 2021-07-06
11:08:06,319 ERROR
Exception inside application: ERR
Error running script (call to
f_00162c6a9d31b171bb0830f1fd769a379c6723d1):
@user_script:6: @user_script: 6:
-MISCONF Redis is configured to save
RDB snapshots, but is currently not
able to persist on disk. Commands that
may modify the data set are disabled.
Please check Redis logs for details
about the error.
- /var/log/redis/redis.conf
2792:M 06 Jul 11:08:01.050 * 10
changes in 300 seconds. Saving...
2792:M 06 Jul 11:08:01.051 # Can't
save in background: fork: Cannot
allocate memory
- ...
- Packages
related
to WebSockets
|
Amazon AWS
|
- Django-compressor: how to write to
S3, read from CloudFront?
- Fun
with
Django storage backends
- Protect some file storage
- Scaling
Django
Apps With Amazon AWS
- Amazon CLI
- Boto
- Amazon
S3 (django-storages)
- See also: Media
- S3Storage
- S3boto (boto)
- Instal·lació / Installation
- Info
- settings.py
INSTALLED_APPS = (
...
'storages',
...
)
####### general
AWS_ACCESS_KEY_ID = '...'
AWS_SECRET_ACCESS_KEY = '...'
AWS_STORAGE_BUCKET_NAME = '...'
# general optional
AWS_QUERYSTRING_AUTH = False
AWS_HEADERS = {
'Cache-Control': 'max-age=86400',
} ####### media: uploaded files
#DEFAULT_FILE_STORAGE =
'storages.backends.s3boto.S3BotoStorage'
DEFAULT_FILE_STORAGE
= 'myapp.s3utils.MediaRootS3BotoStorage'
S3_MEDIA_PATH = "media"
MEDIA_ROOT
= '/%s/' % S3_MEDIA_PATH
# option 1: retrieve media from s3:
# no config is needed
#MEDIA_URL
= "https://%s.s3.amazonaws.com/" %
AWS_STORAGE_BUCKET_NAME
# option 2: retrieve media from cloudfront:
CLOUDFRONT_DOMAIN
=
'xxxxxxx.cloudfront.net'
MEDIA_URL
= 'http://%s/%s/' % (CLOUDFRONT_DOMAIN, S3_MEDIA_PATH )
AWS_S3_CUSTOM_DOMAIN = 'xxxxxxx.cloudfront.net'
####### static: static files
#STATICFILES_STORAGE =
'storages.backends.s3boto.S3BotoStorage'
STATICFILES_STORAGE
= 'myapp. s3utils .StaticRootS3BotoStorage'
S3_STATIC_PATH = "static"
STATIC_ROOT
= '' # option 1: retrieve static
from s3:
STATIC_URL
= "https://%s.s3.amazonaws.com/" %
AWS_STORAGE_BUCKET_NAME
- myapp/s3utils.py
(alternativa / alternative: django-s3-folder-storage)
from
storages.backends.s3boto import S3BotoStorage
from django.utils.functional import
SimpleLazyObject
from django.utils.deconstruct import
deconstructible
from django.conf import settings MediaRootS3BotoStorage
= lambda: S3BotoStorage(location= settings. S3_MEDIA_PATH )
StaticRootS3BotoStorage = lambda:
S3BotoStorage(location= settings. S3_STATIC_PATH
)
@deconstructible
class NewS3BotoStorage(S3BotoStorage):
"""
Deconstructible subclass to
avoid Django 1.7 error:
ValueError:
Cannot serialize:
<storages.backends.s3boto.S3BotoStorage
object...
"""
pass
- myapp/models.py (to override
settings.DEFAULT_FILE_STORAGE)
from myapp.s3utils import
NewS3BotoStorage
class MyModel(models.Model):
poster = models.ImageField(
storage=NewS3BotoStorage(
bucket=settings.DEFAULT_BUCKET_NAME ),
upload_to='posters')
- Multiple S3 buckets
- How
to Dynamically Select Storage in Django FileField
-
|
models.py
|
myapp/s3utils.py |
fixed bucket name (from settings) and
location (from settings):
settings.AWS_STORAGE_BUCKET_NAME
settings.AWS_LOCATION |
class MyModel(models.Model):
image =
models.FileField(
storage=S3Boto3Storage(),
upload_to='mydir',
)
|
|
fixed bucket name (from settings) and
location (not from settings):
settings.AWS_STORAGE_BUCKET_NAME |
class MyModel(models.Model):
image =
models.FileField(
storage=MyS3Boto3Storage(),
upload_to='mydir',
) |
from storages.backends.s3boto3
import S3Boto3Storage
@deconstructible
class
MyS3Boto3Storage(S3Boto3Storage):
location='mylocation' |
dynamic bucket name |
class MyModel(models.Model):
image =
MyDynamicFileField(
storage=MyConfigurableS3Boto3Storage(),
upload_to='mydir',
) |
from django.db import models
from django.db.models.fields.files
import FieldFile
from storages.backends.s3boto3 import
S3Boto3Storage
@deconstructible
class
MyConfigurableS3Boto3Storage(S3Boto3Storage):
def __init__(self,
*args, **kwargs):
# if not passed as kwargs, get values
from settings
kwargs['bucket_name'] =
kwargs.get('bucket_name',
getattr(settings,
'AWS_STORAGE_BUCKET_NAME'))
kwargs['file_overwrite'] =
kwargs.get('file_overwrite',
getattr(settings,
'AWS_S3_FILE_OVERWRITE'))
kwargs['custom_domain'] =
kwargs.get('custom_domain',
getattr(settings,
'AWS_S3_CUSTOM_DOMAIN'))
super(MyConfigurableS3Boto3Storage,
self).__init__(*args, **kwargs)
class MyDynamicFieldFile(FieldFile):
def __init__(self,
instance, field, name):
super(MyDynamicFieldFile,
self).__init__(
instance, field, name
)
# get bucket_name, custom_domain (e.g.
may be based on instance, of type
MyModel)
bucket_name, custom_domain =
_get_storage(instance)
if bucket_name and custom_domain:
# use storage with specific
bucket_name and custom_domain
self.storage =
MyConfigurableS3Boto3Storage(bucket_name=bucket_name,
custom_domain=custom_domain)
else:
# use storage with default values
self.storage =
MyConfigurableS3Boto3Storage()
class
MyDynamicFileField(models.FileField):
attr_class =
MyDynamicFieldFile
def pre_save(self,
model_instance, ):
bucket_name, custom_domain =
_get_storage(model_instance)
if bucket_name and custom_domain:
# use storage with specific
bucket_name and custom_domain
self.storage = MyConfigurableS3Boto3Storage (bucket_name=bucket_name,
custom_domain=custom_domain)
else:
# use storage with default values
self.storage =
MyConfigurableS3Boto3Storage ()
file = super(
MyDynamicFileField ,
self
).pre_save(model_instance, add)
return file
|
- django-storages
with
multiple S3 Buckets
- my_app/s3utils.py
from
storages.backends.s3boto3 import
S3Boto3Storage
from django.conf import settings
from django.utils.deconstruct import
deconstructible
@deconstructible
class MyFirstBucketS3Storage(S3Boto3Storage):
def __init__(self, *args,
**kwargs):
kwargs['bucket'] = 'myfirstbucket'
kwargs['file_overwrite'] = False
kwargs['custom_domain'] =
'myfirstbucket.mydomain.com'
super(MyFirstBucketS3Storage,
self).__init__(*args, **kwargs)
@deconstructible
class MySecondBucketS3Storage(S3Boto3Storage):
def __init__(self, *args,
**kwargs):
kwargs['bucket'] = 'mysecondbucket'
kwargs['file_overwrite'] = False
kwargs['custom_domain'] =
'mysecondbucket.mydomain.com'
super(MySecondBucketS3Storage,
self).__init__(*args, **kwargs)
- my_app/models.py
from s3utils import
MyFirstS3Storage, MySecondS3Storage
class MyModel(models.Model):
my_first_field =
models.FileField(storage=MyFirstS3Storage(),
upload_to=...)
my_second_field =
models.FileField(storage=MySecondS3Storage(),
upload_to=...)
my_other_field = models.FileField()
# uses default storage
- Views
- Problemes / Problems
[Errno 104] Connection reset by peer
- les URLs de l'S3BotoStorage són molt llargues /
URLs from S3BotoStorage are very long
- Using
django-storages and the s3boto backend:
x-amz-security-token is appended which I do
not want
- Solució / Solution 1:
- pot ser que encara quedi
x-amz-security-token
/ maybe x-amz-security-token
is still present
- settings.py
# remove
querystrings in AWS S3 urls
AWS_QUERYSTRING_AUTH = False
# not working to avoid
x-amz-security-token?:
#AWS_S3_SECURE_URLS = False
- Solució / Solution 2:
- no afegeix ni
x-amz-security-token
/ does not append even x-amz-security-token
- settings.py
AWS_STORAGE_BUCKET_NAME
= 'mybucketname'
AWS_S3_CUSTOM_DOMAIN =
'%s.s3.amazonaws.com' %
AWS_STORAGE_BUCKET_NAME
- Upgrading
to
Django 1.7. Getting error: Cannot serialize:
<storages.backends.s3boto.S3BotoStorage object
- EmailMessage
attach_file(): NotImplementedError
- Solving
django-storage NotImplementedError with Amazon
S3
- Solució / Solution
- .py
from
django.core.files.storage import
default_storage as storage
class NewEmailMessage(EmailMessage):
def
attach_file(self, name,
mimetype=None):
"""Attaches a file from the
filesystem."""
filename = os.path.basename(name)
#with open(path, 'rb') as f:
with storage.open(name) as f:
content = f.read()
self.attach(filename, content,
mimetype)
- .py
from ...
import NewEmailMessage
...
@receiver(post_save, sender=MyModel)
def mymodel_post_save(sender,
instance, **kwargs):
...
msg =
NewEmailMessage(...)
if instance.myfile:
msg.attach_file(instance.myfile.name)
msg.send(False)
- Django-compressor: how to
write to S3, read from CloudFront?
- Amazon
Cloudfront
|
http://www.francescpinyol.cat/django.html
Primera versió: / First version: 18.XII.2012
Darrera modificació: 22 de novembre de 2024 / Last update: 22nd
November 2024
FPM
|