Django
Seccions d'aquesta pàgina 38
- 01Django
- 02MVC
- 03Exemple Polls / Polls example
- 04Fitxers estàtics / Static files
- 05Media
- 06Vídeo / Video
- 07Imatges / Images
- 08Generació al vol de camps / On the fly field generation
- 09Eclipse
- 10Bases de dades / Databases
- 11Desplegament servidor HTTP / HTTP server deployment
- 12Settings
- 13Timezone
- 14CORS
- 15Plantilles / Templates
- 16Forms
- 17Seguretat / Security
- 18Usuaris / Users
- 19Admin
- 20I18n / l10n
- 21Tests
- 22REST
- 23Loggers
- 24Activitat i notificacions / Activity and notifications
- 25Correu electrònic / Email
- 26Push notifications
- 27Slack
- 28Tasques / Tasks
- 29Cron
- 30Serialització / Serialization
- 31Importació i exportació / Import and export
- 32Gràfiques / Charts
- 33SMS
- 34Storage
- 35Etiquetes / Tags
- 36Metadades / Metadata
- 37Push
- 38Amazon AWS
01Django#
- Django
-
- Alternatives
- Instal·lació / Installation
- using pyenv
- install pyenv
- cd ~/src
- create virtualenv:
pyenv virtualenv 3.10 mysite-3.10
- temporarily use created virtualenv
pyenv shell mystite-3.10
- install django
pip install django
- create django project (estructura de directoris)
django-admin startproject mysite
- set pyenv permanently:
cd mysitepyenv local mysite-3.10
- Install Python
- optionally: PyDev (Eclipse plugin)
- Install Django
- one of the following:
- from linux distribution packages
urpmi python-django
- easy_install:
easy_install --uprade django
- pip
- virtualenv + pip
- system-wide
# virtualenv /opt/PYTHON27
# source /opt/PYTHON27/bin/activate
- project-based
cd my_projectvirtualenv venvsource venv/bin/activate
# pip install Django# pip install Django==1.7.7# deactivate
- system-wide
- from linux distribution packages
- 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:
- MySQLdb
- Django Rest Framework
- one of the following:
- virtualenv
- using pyenv
- Biblioteques
addicionals / Additional libraries
- Django packages
- 33 projects that make developing django apps awesome
- Debug
- Geografia
- Llengües / Languages
- Països / Countries
- django-countries
- django-diplomat
- countries, languages
- Diners / Money
- django-money
- py-moneyed
- Deprecation
- 1.0: moneyed.localization -> moneyed.localization
- djangorestframework
- serializers.py
from rest_framework import serializers
from moneyed import Money
class MoneySerializer(serializers.Serializer):
"""
Serializer for Money
"""
amount = serializers.FloatField( help_text="Amount of money" )
currency = serializers.CharField( help_text="Currency of money" )
# deserialization
def to_internal_value(self, data):
money = Money(amount=float(data['amount']), currency=data['currency'])
return money
- serializers.py
- django-money
- VAT number
- Info
- VAT identification number (wp)
- VIES VAT number validation (VAT Information Exchange System)
- European
VAT Numbers (O'Reilly)
- regular expressions
- Paquets / Packages
- django-internationalflavor
- data.py (regular expressions for all countries)
- django-internationalflavor
- Info
- Calendari / Calendar
- fullcalendar
- Events (RFC 2445) (RFC
5545) (based on
dateutils.rrule)- django-swingtime
- not for storing recurrence information in database
- django-recurrence
- Date only; no DateTime:
- Major Possible Update: Support For Times. #163
- Abandoned?
#287
- use django-recurring when you need DateTime
- Date only; no DateTime:
- django-recurring
- django-swingtime
- Singleton
- Notificacions / Notifications
- Gràfiques / Charts
- SMS
- Vídeo / Video
- Emmagatzematge / Storage
- Etiquetes / Tags
- Documentació / Documentation
- Release
notes
- verify django version
if django.VERSION[0] > 2:
...
- Django x
-
Django 1.8 Django >= 1.10 mydir/myproject/settings.py
-
INSTALLED_APPS = [
"polls",
]
mydir/myproject/settings.py
-
INSTALLED_APPS = [
"polls.apps.PollsConfig",
]
urls.py
mydir/myapp/urls.py
from . import views
mydir/myapp/views.py
from .models import Question
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- ...
-
- Django 4
-
Django < 4 Django 4 NullBooleanField() BooleanField(null=True) JsonField (postgresql)
JsonField (django)
djangorestframework-jwt djangorestframework-simplejwt (*) force_text force_str from django.conf.urls import url from django.urls import re_path django.core.cache.backends.memcached.MemcachedCache (removed)
django.core.cache.backends.memcached.PyLibMCCache (compatible with django 1, 2)
django.core.cache.backends.memcached.PyMemcacheCache (new from django 3.2)Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- ...
-
- Django 3
-
Django < 3 Django 3 from django.utils import siximport sixfrom django.shortcuts import render_to_response
(removed) use render() with request as additional parameter
# will be removed in Django 4.0
django.contrib.postgres.fields.JSONFielddjango.db.models.JSONFieldHttpResponse._headersHttpResponse.headersAquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
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/*.pyperl -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)
- option 1: modify all migration files
(using perl and lookahead regex)
- 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'- ImportError: No module named 'django.core.urlresolvers'
from django.urls import reverse
<class 'myapp.admin.MyModelAdmin'>: (admin.E012) There are duplicate field(s) in 'fieldsets[15][1]'.- ...
- django.core.exceptions.ImproperlyConfigured:
Specifying a namespace in include() without
providing an app_name is not supported. Set the
app_name attribute in the included module, or pass a
2-tuple containing the list of patterns and app_name
instead.
- in included urls.py, add
- app_name = 'myapp'
- in included urls.py, add
-
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 (4.1.0 Breaking changes) 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)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Problemes / Problems
Error: database connection isn't set to UTC- Solució / Solution
pip install 'psycopg2<2.9'
- Solució / Solution
- 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.pyx django-activity-stream
0.6.3 0.10.0
1.4.12.0.0 1.4.0
2.0.0django-admin-csvexport
1.11 2.2 2.2
django-allauth
x 0.40.0 0.40.0
0.54.00.63.6
django-appconf
1.0.3 1.0.3
1.0.6
--
django-autoslug
1.9.8 1.9.8
1.9.91.9.9
django-axes
5.3.3 5.3.3
5.27.06.4.0
django-cors-headers
2.1.0 2.1.0
3.11.04.3.1
django-countries
5.1.1 5.1.1
7.6.17.6.1
django-extra-fields
2.0.5 2.0.5
3.0.23.0.2
django-filter
2.2.0 2.2.0
21.123.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.12.0.1
django-jsonfield-backport
- 1.0.5 -
django-mass-edit
3.2.0 3.2.0
3.5.03.5.0
django-model-utils
3.0.0 3.0.0
4.0.0
4.1.0 (breaking changes)
4.2.04.5.1
5.0.0 (TypeError: __class__ assignment: 'NewMultilingualManager' object layout differs from 'SoftDeletableManager' #636)5.0.0
django-modeltranslation
0.14.4 0.14.4
0.18.20.18.11
django-modeltree
- 0.5 0.5
django-money
2.0.3 2.0.3
3.5.33.5.2
django-nested-inline
0.3.7 0.3.7
0.4.60.4.6
django-notifications-hq
1.4.0
1.51.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.02.2.0
django-sslserver
0.19 0.19
0.220.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.13.15.1
djangorestframework-csv
2.1.0 2.1.0
3.0.23.0.2
djangorestframework-gis
0.15 0.15
1.01.0
djangorestframework-jwt
1.10.0 1.10.0
1.11.01.11.0
drf-extensions
0.4.0
0.5.00.4.0
0.7.10.7.1
drf-nested-routers
0.91 0.91
0.93.40.94.0
drf-yasg
1.17.1
1.20.01.17.1
1.21.71.21.7
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- Django 1.10, 1.11
-
last version that
supports Django 1.11notes 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-backportdjango-autoslug 1.9.8
django-axes 5.3.3 File "[...]/lib/python3.8/site-packages/axes/checks.py", line 75, in axes_middleware_checkWhen upgrading to django-axes v5:
if "axes.middleware.AxesMiddleware" not in settings.MIDDLEWARE:
TypeError: argument of type 'NoneType' is not iterable
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
- or overwrite widget, to keep using
_0, _1:
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 ?
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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'^', ...
]
- old style:
- 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):
- old
- "
-
- Django 1.9
- Django 1.7 release notes
- Django 1.6 release notes
- verify django version
- Migració de python 2 a 3 /
Python 2 to 3 migration
- Python 3
- Issues
- __unicode__
- __str__() and __unicode__() methods (Django 1.11)
- __unicode__
- Paquets / Packages
-
last version that
supports Python 2.7comments 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
...
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- ...
- Tutorials
- Django
documentation (1.6)
(1.5)
- First steps
- The
model
layer
- Models
- Model
inheritance
- Abstract base classes
- Multi-table inheritance
- Proxy models
- Inheritance
manager (django-model-utils)
place = Place.objects.get_subclass(id=some_id)nearby_places = Place.objects.filter(location='here').select_subclasses()
- shared pk by inhereted classes
- Model
inheritance
- Model field reference
- Model meta options
- unique_together
- unique_together with foreign key
- validate_unique
(no need to overwrite save()) (currently
not working with django-nested-inline)
from django.core.validators import ValidationError
from django.core.exceptions import NON_FIELD_ERRORS
class DVB_Service(models.Model):
original_network = models.ForeignKey(DVB_OriginalNetwork)
service_id = models.IntegerField(_("service_id"))
PCR_PID = models.IntegerField(_("PCR_PID"), blank=True, null=True)
def validate_unique(self, *args, **kwargs):
super(DVB_Service, self).validate_unique(*args, **kwargs)
if not self.pk:
qs = DVB_Service.objects.filter(service_id=self.service_id)
if qs.filter(original_network__original_network_id=self.original_network.original_network_id).exists():
raise ValidationError({NON_FIELD_ERRORS:'service_id must be unique per original_network_id'})
- Django: overriding Model.validate_unique
- How to validate uniqueness constraint across foreign key (django)
- Django Unique Together (with foreign keys)
- validate_unique
(no need to overwrite save()) (currently
not working with django-nested-inline)
- unique_together with foreign key
- unique_together
- Models
- 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
- settings.py
- Load testing
- Throttling
- Django
project optimization guide (part 1)
- Geographic framework
- GeoDjango
- GeoDjango
installation
- With PostgreSQL
- Install and setup PostGis
- Mageia
urpmi libgdal-devel
- Installing PostGIS
- Mageia
- Python requirements
- Mageia
urpmi postgresql9.4-devel
- CentOS
yum install postgresql-devel
pip install psycopg2
- Mageia
- 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
- Mageia
- grant permissions to
your_django_user
your_django_db=# GRANT SELECT ON spatial_ref_sys TO your_django_user;
- Connect to database as
root user:
- Solution
- When filtering (GET), http server
response is: "
- Install and setup PostGis
- With PostgreSQL
- Admin
- Inline admin
- Admin with OSMGeoAdmin
(Open Street Map)
- requisites
- GDAL
- Installation from source
- Mageia
- urpmi gdal
- PROJ.4
- Installation from source
- Mageia
- urpmi proj
- GDAL
- requisites
- Tutorial
- requisites
- Mageia
- urpmi gdal
- Mageia
- GeoDjango Tutorial
- requisites
- 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 adminadmin.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',)
- settings.py
- Filters
-
database
distance_filter_convert_meters
lat-lon
WGS84
SRID 4326
True
default
meters
SRID 3875 (900913) False
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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',
)
- settings.py
- Installation
- djangorestframework-gis
- GeoDjango
installation
- GeoDjango
- Other core functionalities
- Middleware
- Using
Django
- django-admin
export PYTHONPATH="/absolute/path/to/mysite:$PYTHONPATH"
django-admin ... --settings=mysite.settingsexport DJANGO_SETTINGS_MODULE=mysite.settings
django-admin ...
- django-admin and manage.py
- Examples
- ...
- 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")
- my_app/management/commands/my_command.py
- Exemple / Example
- ...
- Writing
custom django-admin commands
- Models and databases
- ...
- django-admin
- Django snippets
- "How-to" guides
- Trespams: Django
- contrib packages
- Release
notes
- Resum
- New project
django-admin.py startproject my-project
- New application
cd my-projectpython manage.py startapp my-app
- New project
- 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
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
djcode_1.4
mysite
manage.py
sqlite.db
mysite
__init__.py
settings.py
urls.py
wsgi.py
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
polls
__init__.py
models.py
tests.py
views.py
urls.py
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
.project
.pydevproject
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- project:
django-admin.py startproject mysite - application:
python manage.py startapp polls - Eclipse files (PyDev)
-
step 1: install django step 2: create django project and use it from eclipse
option 1: create django project from cli + use eclipse option 2: create django project from eclipse Python
structure
python (virtualenv) from cli create django project from cli
use Eclipse
create django project from Eclipse sytem-wide python
1. [python], project - src/
- mysite/
- manage.py
- mysite/
- __init__.py
- settings.py
- urls.py
- wsgi.py
- mysite/
- cd src
- django-admin startproject mysite
- New / Project...
- PyDev Project (to import an existing Django project, not an Eclipse project yet)
- Project Name: mysite
- Directory: src/mysite (autocompleted)
- Django Version: 1.4 or later
- Properties -> PyDev-PYTHONPATH -> String
Substitution Variables -> Add variable
DJANGO_MANAGE_LOCATION: manage.py(this will allow project to be run/debug as Django)DJANGO_SETTINGS_MODULE: my_project.settings
- New / Project...
- PyDev Django Project
- Project Name: mysite
- Directory: src/mysite (autocompleted)
- Django Version: 1.4 or later
2. app - src/
- mysite/
- ...
- myapp/
- admin.py
- apps.py
- __init__.py
- models.py
- tests.py
- views.py
- migrations/
- __init__.py
- mysite/
- cd src
- cd mysite
- ./manage.py startapp myapp
- Django -> Create application: myapp
virtualenv inside project
1. python virtualenv - src/
- mysite/
- env/
- ...
- env/
- mysite/
- cd src
- mkdir mysite
- virtualenv env
- source env/bin/activate
- pip install --upgrade pip
- pip install django
2. project - src/
- mysite/
- env/
- ...
- manage.py
- mysite
- __init__.py
- settings.py
- urls.py
- wsgi.py
- env/
- mysite/
- cd src
- cd mysite
- django-admin startproject mysite .
- New / Project...
- PyDev Project
- Project Name: mysite
- Directory: src
- Interpreter: ~/src/mysite/env/bin/python
3. app
virtualenv in pyenv 1. python virtualenv - ~/.pyenv/
- versions/
- mysite-3.10/
- versions/
- pyenv virtualenv 3.10 mysite-3.10
- pyenv shell mysite-3.10
- pip install django
2. project (method a) - src/
- mysite/
- manage.py
- mysite/
- __init__.py
- settings.py
- urls.py
- wsgi.py
- awsgi.py
- mysite/
- cd src
- mkdir mysite
- cd mysite
- pyenv local mysite-3.10
- django-admin startproject mysite .
- New / Project...
- PyDev Project (to import an existing Django project, not an Eclipse project yet)
- Project Name: mysite
- Directory: src
- Interpreter: mysite-3.10
(segurament l'haureu de crear: Click here to configure an interpreter not listed) - (select project): PyDev -> Set as Django Project
- Properties -> PyDev-PYTHONPATH -> String
Substitution Variables -> Add variable
DJANGO_MANAGE_LOCATION: manage.py(this will allow project to be run/debug as Django)DJANGO_SETTINGS_MODULE: my_project.settings
- New / Project...
- PyDev Django Project
- Project Name: mysite
- Directory: src
- Interpreter: mysite-3.10
(segurament l'haureu de crear: Click here to configure an interpreter not listed) - (Properties are already set)
2. project (method b) - src/
- mydir/
- manage.py
- mysite_b/
- __init__.py
- settings.py
- urls.py
- wsgi.py
- awsgi.py (django>=3)
- mydir/
- cd src
- mkdir mydir
- cd mydir
- pyenv local mydir-3.10
- django-admin startproject mysite_b .
- New / Project...
- PyDev Project (to import an existing Django project, not an Eclipse project yet)
- Project Name: mydir
- Directory: .../src/mydir
- Interpreter: mydir-3.10
(segurament l'haureu de crear: Click here to configure an interpreter not listed) - this will create:
.project: to indicate that this is an Eclipse project.pydevproject: to indicate that this is a PyDev project
- (select project): PyDev -> Set as Django
Project
- this will activate Django menu
- will add to .project:
<nature>org.python.pydev.django.djangoNature</nature>
- Properties -> PyDev-PYTHONPATH -> String
Substitution Variables -> Add variable
DJANGO_MANAGE_LOCATION: manage.py(this will allow project to be run/debug as Django)- if not set, it will be prompted with a
default value of manage.py
the first time we run a Django custom command (e.g. check)
- if not set, it will be prompted with a
default value of manage.py
DJANGO_SETTINGS_MODULE: mysite_b.settings(needed bacause default value is:mydir.settings)
3. app
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- src/
-
- Creació d'un projecte / Start a project (a project contains
several apps)
cd[~/src/]djcode- create the project ("mysite"):
django-admin.py startproject mysitecd mysitepython manage.py runserver- http://127.0.0.1:8000/
- configuració de les
bases de dades /
database setup:
- SQLite
mysite/settings.py-
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': '/absolute/path/to/polls.db', # absolute path to database file if using sqlite3.
}
} INSTALLED_APPS = (
...
)
-
- PostgreSQL
- MariaDB / MySQL
- Creeu
un
usuari, una contrasenya i una base de dades /
Create user (
user_name) and password (password_for_user_name) and a database (database_name)- option 1: manually
mysql -u root -p -h localhostCREATE 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
- mariadb_create.sh
- option 1: manually
- 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 = (
...
)
-
- Creeu
un
usuari, una contrasenya i una base de dades /
Create user (
- create the tables on the database (one table for each
app in INSTALLED_APPS list)
- Django >= 1.7
python manage.py migratepython manage.py createsuperuser
- Django < 1.7
- Django >= 1.7
- 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
- ./esborra_base_dades.sh
- SQLite
- administration:
mysite/settings.py(activated by default in Django 1.6)INSTALLED_APPS = (
...
'django.contrib.admin',
)
mysite/urls.py(activated by default in Django 1.6)from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
...
url(r'^admin/', include(admin.site.urls)),
)
- update the database:
python manage.py syncdb
- check that it is working:
python manage.py runserver- http://127.0.0.1:8000/admin/
- poll application administration:
- polls/admin.py
- personalise the look and feel of the admin pages:
mysite/settings.pyTEMPLATE_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/mysitepython manage.py startapp polls- modify general files:
mysite/mysite/settings.pyINSTALLED_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 pollspython manage.py validatepython manage.py sqlcustom pollspython manage.py sqlclear pollspython manage.py sqlindexes pollspython manage.py sqlall polls
- create the polls tables into the database:
python manage.py syncdb
- other django-admin manage.py
functions:
- list of available functions
python manage.py help
- call the API from command line:
- if you are using virtualenv:
source env/bin/activate
python manage.py shell(sets up the environment)from my_app.models import *
python manage.py shell <toto.py- toto.py
from my_app.models import *
m = MyModel.objects.filter(...)
print m
...
- toto.py
- 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
- if you are using virtualenv:
- create a (or several) superuser(s):
python manage.py createsuperuser
- check
python manage.py check- Problemes / Problems
ImportError: cannot import name 'MyModel' from partially initialized module 'my_app.models' (most likely due to a circular import) (/path/to/my_app/models.py)- Solucions / Solutions
MyModel = apps.get_model("my_app", "MyModel")class MyOtherModel(models.Model)
my_relation = models.ForeignKey(
# NOTE: it must be specified inside quotes (lazy reference) to avoid circular import issues
"my_app.MyModel",
)
- Solucions / Solutions
- Problemes / Problems
- browse the database:
python manage.py dbshell
- validate:
python manage.py validate
- inspect database:
python manage.py inspectdb
- ordres pròpies
/ custom commands
- Writing
custom django-admin commands
cd myproject/my_appmkdir managementtouch __init__.pymkdir commandstouch __init__.pymy_command.pyfrom 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
- Writing
custom django-admin commands
- list of available functions
- Request and response objects
- Built-in
template
tags and filters
- block, cycle (for), ...
- How
to
access mod_ssl environment variables from django using
mod_wsgi?
cd mods-enabled; sudo ln -s ../mods-available/headers.load .- default-ssl[.conf]
SSLOptions +stdEnvVars- Default available variables:
- SSL_... (client related)
- to add more variables:
RequestHeader set SSL_CLIENT_VERIFY %{SSL_CLIENT_VERIFY}e- ...
- Accessing the CGI environment variables created by mod_ssl from within Plone
- Javascript: window.onload
- service/template/main.html
{% block extrahead %}
<script type="text/javascript">
function carrega() { ... }
[...]
{% endblock %}{%block onload %}carrega(){% endblock %}
- service/template/main.html
- Desplegament / Deployment
- How Do I Create a Python / Django Egg?
- Advanced tutorial: How to write reusable apps (Django)
- Tutorial on Packaging and Distributing Projects (Python)
- favicon
- How can I get a favicon to show up in my django app?
- e.g. templates/admin/base_site.html
{% extends "admin/base.html" %}
{% load static %}
{% block extrahead %}
<link rel="shortcut icon" href="{% static 'favicon.png' %}"/>
{% endblock %}
- my_site/my_site/settings.py
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static_general"),
)
- my_site/static_general/favicon.png
- 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 envsource env/bin/activatepip install --upgrade pip
- install django
pip install django
- create django project:
django-admin startproject mysite .
- create application
./manage.py startapp myapp
- mysite/urls.py
from django.urls import include, path
urlpatterns = [
path('myapp/', include('myapp.urls')),
]
- myapp/urls.py
from django.urls import path
from . import views
app_name = 'myapp'
urlpatterns = [
path('toto/', views.toto, name='toto'),
]
- myapp/views.py
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def toto(request):
try:
primer = request.POST['primer']
except:
return HttpResponse("missing POST parameter: primer", content_type="text/plain")
return HttpResponse("primer: {0}".format(primer), content_type="text/plain")
./manage runservercurl -i -X POST http://localhost:8000/myapp/toto/ -F primer=2
- create dir for project
- Exemples mínims / Minimal examples
-
project
application
access
manage settings urls.py application urls.py models admin application views.py html template ./manage.py runserverhello world view - create dir
cd ~/src
mkdir -p mydir; cd mydir
- install python3 in a virtualenv
virtualenv envsource env/bin/activatepip install --upgrade pip
- install django
pip install "django<3"
- create django project:
django-admin startproject myproject .
- create application
./manage.py startapp myapp
mydir/myproject/urls.py
from django.urls import path, include
urlpatterns = [
path('myapp/', include('myapp.urls')),
]
mydir/myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
mydir/myapp/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the events index.")
- http://127.0.0.1:8000/myapp/
view with parameters from url - create dir
cd ~/src
mkdir -p mydir; cd mydir
- install python3 in a virtualenv
virtualenv envsource env/bin/activatepip install --upgrade pip
- install django
pip install "django<3"
- create django project:
django-admin startproject myproject .
- create application
./manage.py startapp myapp
mydir/myproject/urls.py
from django.urls import path, include
urlpatterns = [
path('myapp/', include('myapp.urls')),
]
mydir/myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('<int:question_id>/', views.detail, name='detail'),
]
mydir/myapp/views.py
from django.http import HttpResponse
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
- http://127.0.0.1:8000/myapp/1/
- http://127.0.0.1:8000/myapp/2/
- ...
view with html template
mydir/myproject/settings.py (needed to find application templates)
INSTALLED_APPS = [
...
'myapp',
]
mydir/myproject/urls.py
from django.urls import include, path
urlpatterns = [
path('myapp/', include('myapp.urls')),
]
mydir/myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
mydir/myapp/views.py
from django.shortcuts import render
def index(request):
context = {'day_number': 4}
return render(request, 'myapp/index.html', context)
mydir/myapp/templates/myapp/index.html
- Today is: {{ day_number }}.
- http://127.0.0.1:8000/myapp/
post view - create dir
- cd ~src
mkdir -p mydir; cd mydir
- cd ~src
- install python3 in a virtualenv
virtualenv envsource env/bin/activatepip install --upgrade pip
- install django
pip install django
- create django project:
django-admin startproject myproject .
- create application
./manage.py startapp myapp
mydir/myproject/urls.py:
from django.urls import include, path
urlpatterns = [
path('myapp/', include('myapp.urls')),
]
mydir/myapp/urls.py:
from django.urls import path
from . import views
app_name = 'myapp'
urlpatterns = [
path('toto/', views.toto, name='toto'),
]
myproject/mysite/myapp/views.py: from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def toto(request):
try:
primer = request.POST['primer']
except:
return HttpResponse("missing POST parameter: primer", content_type="text/plain")
return HttpResponse("primer: {0}".format(primer), content_type="text/plain")
curl -i -X POST http://localhost:8000/myapp/toto/ -F primer=2
calendar html view
(events as an array)- create dir
cd ~/src
mkdir -p calendar_dir; cd calendar_dir
- install python3 in a virtualenv
virtualenv envsource env/bin/activatepip install --upgrade pip
- install django
pip install "django<3"
- create django project:
django-admin startproject calendar_project .
- create application
./manage.py startapp events
- create database
./manage makemigrations./manage migrate
calendar_dir/calendar_project/settings.py
INSTALLED_APPS = [
...
'events',
]
calendar_dir/calendar_project/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('events/', include('events.urls')),
]
calendar_dir/events/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('calendar', views.calendar, name='calendar'),
]
calendar_dir/events/models.py
from django.db import models
class Event(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255,null=True,blank=True)
start = models.DateTimeField(null=True,blank=True)
end = models.DateTimeField(null=True,blank=True)
def __str__(self):
return self.name
calendar_dir/events/admin.py
from django.contrib import admin
from .models import Event
admin.site.register(Event)
calendar_dir/events/views.py
from django.shortcuts import render
from .models import Event
def calendar(request):
all_events = Event.objects.all()
context = {
"events":all_events,
}
return render(request,'events/calendar.html',context)
calendar_dir/events/templates/events/calendar.html
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.css"/>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"></script>
<script>
$(document).ready(function () {
var calendar = $('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
events: [
{% for event in events %}
{
title: "{{ event.name }}",
start: '{{ event.start|date:"c" }}',
end: '{{ event.end|date:"c" }}',
id: '{{ event.id }}',
},
{% endfor %}
],
selectable: true,
selectHelper: true,
editable: true,
eventLimit: true,
});
});
</script>
</head>
<body>
<br/>
<h2 align="center"><a href="#">title</a></h2>
<br/>
<div class="container">
<div id="calendar"></div>
</div>
</body>
</html>
- http://127.0.0.1:8000/admin
- http://127.0.0.1:8000/events/calendar
calendar html view
(events as a json feed)
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('calendar', views.calendar, name='calendar'),
path('event_list', views.event_list, name='event_list'),
]
views.py
import json
from datetime import datetime
from django.shortcuts import render
from django.http import HttpResponse
from .models import Event
def calendar(request):
return render(request,'events/calendar.html')
def event_list(request):
start = request.GET['start']
end = request.GET['end']
// when timezone is set to UTC, requests are of type:
///event_list?start=2020-01-26T00%3A00%3A00Z&end=2020-01-27T00%3A00%3A00Z&timezone=UTC&_=1580059864316 start_datetime = datetime.strptime(start, '%Y-%m-%dT%H:%M:%SZ')
end_datetime = datetime.strptime(end, '%Y-%m-%dT%H:%M:%SZ')
events = Event.objects.filter(start__range=(start_datetime, end_datetime))
result = []
for event in events:
print("- event: {}".format(event.name))
result.append({
'id': event.id,
'title': event.name,
'start': event.start.isoformat(),
'end': event.end.isoformat()
})
return HttpResponse(json.dumps(result), content_type="application/json")
calendar.html
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.css"/>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.10.1/fullcalendar.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.10.1/locale/ca.js"></script>
<script>
$(document).ready(function () {
var calendar = $('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
timezone: 'UTC',
locale: 'ca',
events: 'event_list',
defaultView: 'agendaDay',
eventTimeFormat: 'H:mm', // uppercase H for 24-hour clock
nowIndicator: true,
//slotLabelInterval: {hours: 2},
slotLabelFormat: 'H:mm',
selectable: true,
selectHelper: true,
editable: true,
eventLimit: true,
});
});
</script>
</head>
<body>
<br/>
<h2 align="center"><a href="#">title</a></h2>
<br/>
<div class="container">
<div id="calendar"></div>
</div>
</body>
</html>
library - create dir
cd ~src
mkdir -p library_dir; cd library_dir
- (install python2 in a virtualenv)
virtualenv -p /usr/bin/python2.7 env
- install python3 in a virtualenv
virtualenv envsource env/bin/activatepip install --upgrade pip
- install django
pip install "django<3"
- create django project:
django-admin startproject library_project .
- create application
./manage.py startapp library_app
library_dir/library_project/settings.py
INSTALLED_APPS = [
...
'library_app',
]
library_dir/library_project/urls.py
- ...
library_dir/library_app/urls.py:
- ...
library_dir/library_app/models.py
- ...
library_dir/library_app/admin.py
- ...
library_dir/libray_app/views.py
- ...
- http://127.0.0.1:8000/admin/
- http://127.0.0.1:8000/library_app/
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- create dir
-
02MVC#
| model |
serialization |
view |
urls |
||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| file |
attributes |
type |
class |
attributes |
functions (provided and can be overwritten / must be implemented) |
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 |
|
|
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 |
|
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
|
|
url(r'^admin/',
include(admin.site.urls)), |
||||||||
| serializers.py |
from
rest_framework
import serializers
|
class
Meta: model fields exclude read_only_fields ... |
APIView: (no need to be attached to a model) from rest_framework.views import APIView |
APIView |
|
|
Renderers:
|
(explicitly defined at
derived class)
|
url(... MyAPIView.as_view() ), | ||
| generics: from rest_framework import generics |
generics.GenericAPIView |
|
|
||||||||
| 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 |
#
having a router allows us to declare all views at a time 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 | ||||||||
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
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 dataif 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 Viewfrom 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.UserCreationFormfrom rest_framework import serializers
class UserSerializer (serializers.HyperlinkedModelSerializer)
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
from django.contrib.auth.forms import UserCreationFormdef 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 AuthenticationFormdef 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 ModelFormclass 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 Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- ORM: Object-relational mapping (wp)
- Models
- Polls example models
- Model
Meta
options
- get_latest_by
- ordering
- verbose_name
- verbose_name_plural
- ...
- exemples / examples:
class Toto(models.Model):
...
class Meta:
verbose_name = _("Model toto")
verbose_name_plural = _("Models toto")
ordering = ['-start_date']
- Manager
- create
MyModel.objects.create(...)MyModel.objects.get_or_create(...)- MyModel.objects.update_or_create(...)
- update
my_instance.save()- only specified fields:
my_instance.save(update_fields=[...])
- delete
my_instance.delete()
- create
- Camps / Fields
- Model field reference
- Deprecation
- Considerations when removing model fields
- Deprecating fields in django model
- Exemple / Example
class MyModel(Model):
field_a = ...
field_a.system_check_deprecated_details = {
'msg': 'field_a has been deprecated',
'hint': 'Use MyOtherModel.field_c instead',
'id': 'fields.W900',
}
- Index
- db_index
- composite index
- index_together (deprecated)
- indexes
- 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
- Change the file name
- 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 )
- models.py
- 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 returnFIELD_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 Noneget_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])
- models.py
- Djangorestframework
- DateTimeField/
DateField / TimeField
- Omple automàticament quan es crea:
data = models.DateField(auto_now_add=True)
- Omple automàticament quan s'actualitza:
data = models.DateField(auto_now=True)
- Timezone
- Data / Date
- Date in filter
- Date in Python
- Django DateField
default options
- today() vs today
- Dates in DjangoRestFramework
- How to Use Date Picker with Django
- TimeField
- models.py
class MyModel(models.Model):
duration = models.TimeField()
- admin.py
from django.db import models
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.TimeField: {'widget': None},
}
- models.py
- 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},
}
- models.py
- 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 )
- models.py
- utilització / usage
- Omple automàticament quan es crea:
- 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
- Django Admin: JSONField default empty dict wont save in admin
- Solució:
myfield = JSONField(default=list, blank=True)
- 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(attrs={"style": "resize: both; overflow: auto;"})}
}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}
}
- JSONEditorWidget
- 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)
- default non-empty list
def get_default_list():
# auxiliar function to avoid error:
# ArrayField default should be a callable instead of an instance so that it's not shared between all field instances. HINT: Use a callable instead, e.g. use list instead of []
return ["first", "second"]
class MyModel(models.Model):
my_arrayfield = ArrayField(... default=get_default_list)
- example
- ArrayField
- 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 fieldsclass 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')
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- ForeignKey
- one model can be attached to only one kind of model
class Toto(models.Model):
user = models.ForeignKey(MyUser, verbose_name=_("User"), help_text=_("This is an extended help"), related_name='myuser')- Admin ForeignKey
- related_name
- on_delete
- What does adding on_delete to models.py do, and what should I put in it? [duplicate]
- mandatory from Django 2
- when parent instance is deleted, also delete
child instance (was default on Django<2):
class ChildModel(models.Model):
parent = models.ForeignKey(ParentModel, on_delete=models.CASCADE)
- when parent instance is deleted, do not delete
child instance:
class ChildModel(models.Model):
parent = models.ForeignKey(ParentModel, on_delete=models.SET_NULL, null=True)
- GenericForeignKey
- one model can be attached to several kind of models
- Admin GenericForeignKey
- Avoid
Django's GenericForeignKey
- non trivial filtering
- Generic relations (The contenttypes framework)
- How to Use Django's Generic Relations
- How to use GenericForeignKey in Django
- on_delete
- Reverse
generic relations
- "Unlike ForeignKey, GenericForeignKey does not accept an on_delete argument to customize this behavior; if desired, you can avoid the cascade-deletion by not using GenericRelation, and alternate behavior can be provided via the pre_delete signal."
- Reverse
generic relations
- ContentType
- Get model from content type:
my_content_type.model_class()
- Get content type from model (or instance):
ContentType.objects.get_for_model(MyModel)
- Get model from content type:
- 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.tagclass 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.
- How to traverse a GenericForeignKey in Django?
- django-notifications
- Solució / Solution
- ...
- 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
classTata(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
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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
- direct
- do something when the manytomany relation changes
- Serializer: aniuat i filtrat / nested and filtered (ManyToMany)
- Queries: query with a condition in ManyToMany
- Encrypted fields
- Encryption & Hashing In Django
-
... django-cryptography
django-crypto-fields pycryptodome django-cryptographic-fields
django-encrypted-model-fields
(fork from django-cryptographic-fields)
django-fernet-fields
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- ...
- 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()
- protect with transaction.atomic() (Controlling
transactions explicitly):
- To avoid message "An error occurred in the
current transaction. You can't execute queries
until the end of the 'atomic' block.":
- ...
- Problem with atomic transactions
- IntegrityError
- Grafs / Graphs
- Django - Model graphic representation (ERD)
- django-extensions
- docs
- instal·lació / installation
pip uninstall pyparsingpip install pyparsing==1.5.7pip install pydotpip install "django-extensions<3" "Django<2"- settings.py
INSTALLED_APPS = (
...
'django_extensions',
...
)
- Ús / Usage
runscriptshow_urls- show all endpoints and reverse
...
- Graph
models
- instal·lació / installation
- Mageia
urpmi graphviz
- Mageia
- utilització / usage
python manage.py graph_models my_app | dot -Tpdf > my_app.pdf
- instal·lació / installation
- django-graphviz
- Dades inicials / Initial data
- Providing
initial
data for models
- Migrations
- Fixtures
- Providing initial data with fixtures
- create the data files from existing database
python manage.py dumpdata > my_project_yyyymmdd.jsonpython manage.py dumpdata auth > auth_yyyymmdd.jsonpython manage.py dumpdata my_app > my_app_yyyymmdd.json
- Fixtures in unit tests
- manage.py custom command
- Providing
initial
data for models
- Sites
- The
sites
framework
- activate it (Django >=1.6)
- settings.py
- INSTALLED_APPS = (
'django.contrib.sites',
) - SITE_ID = 1
- INSTALLED_APPS = (
- python manage.py migrate
- settings.py
- 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
- will create
python manage.py makemigrations --empty sites- will create
my_project/migrations/sites/0002_auto_yyyymmdd_hhmm.py
- will create
- 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)
]
- settings.py
- 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)
- with request
- activate it (Django >=1.6)
- The
sites
framework
- 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)
- models.py
- Example: m2m
- models.py
- ..
- models.py
- Escriptura i lectura
a/des de la base de dades / Writing and reading to/from
database
- Escriptura: un model s'escriu a la base de dades quan passa per django/db/transaction.py: __exit__().connection_commit()
- des de l'admin això no es fa fins que es retorna la petició; hi pot haver comportaments estranys si mentre encara no s'ha desat l'objecte es fa una petició a la base de dades (per exemple una petició a un endpoint disparada dins del save o del post_save)
- Queries
- Making
queries
- QuerySet
API
-
math notation
django < 1.11
django >= 1.11
Union
qs = qs1 | qs2
union()
Intersection
- qs1 & qs2
- filter()
- exclude()
intersection()
Difference
qs1 \ qs2
qs1.exclude(pk__in=qs2)
difference()
Empty
qs = MyModel.objects.none()Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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)
- dynamically build Q expression (if
it applies):
- Solució / Solution
- Problemes / Problems
- Exemples / Examples
- Django
ORM: what are its less obvious features?
- Conditional aggregation
- Django QuerySets: Fucking Awesome? Yes
- Django
ORM: what are its less obvious features?
- .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)
- filtrar per alguna característica dels objectes relationats
- 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 infirst_qs_f1_f2)
)
second_qs = MyModel.objects.filter(user=None).exclude(intersection_taking_f1_f2)
final_qs = first_qs | second_qs
- given the following records ... :
- Aggregation
(Aggregation
functions)
- .complex_filter()
- .exclude()
- __in=
- order_by
- F() expressions (1.7, 1.8)
- Complex
lookups
with Q objects
from django.db.models import Q- Q()
- NOT
~Q(...)
- OR
Q(...) | Q(...)
- AND
Q(...) & Q(...)
- GCM
devices belonging to people with a given name and
joining day before today minus 5 days:
gcm_devices = GCMDevice.objects.filter(person__in=( Person.objects.filter(name=given_value,membership__date_joined__lt=datetime.date.today()-datetime.timedelta(days=5) ) ) )
- Dates
- Django: Using F arguments in datetime.timedelta inside a query
- Hybrid (filter/Python):
# local time:
now = datetime.datetime.now()
now_date = now.date()
now_time = now.time()
now_weekday = now.weekday()
# Django query
# get all valid events
events = Event.objects.filter(
start_date__lte = now_date,
end_date__gte = now_date,
weekdays__contains = now_weekday
)
# refine query in Python
# check if an event is active now
active_events = []
for event in events:
datetime_start = datetime.datetime.combine(now_date, event.start_time)
duration = event.duration
datetime_end = datetime_start + datetime.timedelta( hours=duration.hour, minutes=duration.minute, seconds=duration.second )
print '%s: %s - %s' % (event.name, datetime_start, datetime_end)
if (datetime_start <= now) and (datetime_end >= now):
active_events.append(event)
print active_events
-
- QuerySet
API
- 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?
- Making
queries
- 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:
- django-extensions: show_urls
- 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')
- my_project/my_project/urls.py
- 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={'...':'...'})
- my_project/my_project/urls.py
- 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')
- my_project/my_project/urls.py
- admin
- Reversing admin URLs
-
url
parameters
example
python
template
context = {}
# opts may be used by {% url opts|...
context["opts"] = self.model._meta # needed by admin_urlname in templateAdminSite
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" %}
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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 importMyModelListView
url(r'^mymodels/$',MyModelListView.as_view()),from my_app.views importMyModelListView
url(r'^mymodels/$',MyModelListView.as_view(template_name = 'your_template.html')),
from django.views import genericclass 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 importMyModelListView
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 genericclass MyModelCreateView(generic.CreateView):
model = MyModel- <model_name>_form.html (or specified by template_name)
{prefix}/{lookup}/ GET
retrieve
from django.views import genericclass MyModelDetailView(generic.DetailView):
model = MyModel
PUT
update
PATCH
partial_update
DELETE
destroy
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Polls example views
- Function-based views
- request
- return response
type
return ...
import
"toto"
HttpResponse("value: %s" % toto)from django.http import HttpResponsetext/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 renderTemplateResponse, SimpleTemplateResponse
application/json
(or default renderer)
Response(serializer.data, status=status.HTTP_...)
djangorestframework
text/html render( Response(...) )
application/ms-excel
xlwt
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Class-based
views (API: Class-based
views) (see also: REST
routers)
- Introduction to class-based views (API: Base
views: View, TemplateVIew, RedirectView)
- Decorating
class-based
views
login_required, permission_required@method_decorator(login_required)
- Decorating
class-based
views
- Class-based generic views (API: Generic display views:DetailView, ListView)
- Form handling with class-based views
- Using mixins with class-based views
- Introduction to class-based views (API: Base
views: View, TemplateVIew, RedirectView)
- 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)
- Getting “This QueryDict instance is immutable” on one Django view but not another
- django
- why is the request.POST object immutable?
- workaround
if hasattr(request.data, '_mutable'):
mutable = request.data._mutable
request.data._mutable = True
request.data['some_data'] = 'test data'
request.data._mutable = mutable
else:
request.data['some_data'] = 'test data'
- workaround
- 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 Resourcefrom polls.models import Poll
from resourceexample.forms import MyFormfrom 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__(...)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
03Exemple Polls / Polls example#
- djcode/mysite
- manage.py
- urls.py (URLconf)
from django.conf.urls.defaults import patterns, include, url
# admin
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^polls/$', 'polls.views.index'),
url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
url(r'^admin/', include(admin.site.urls)),
)from django.conf.urls.defaults import patterns, include, url
# admin
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('polls.views',
url(r'^polls/$', 'index'),
url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
)
urlpatterns += patterns('',
url(r'^admin/', include(admin.site.urls)),
)# This also imports the include function
from django.conf.urls.defaults import *
# admin
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^polls/', include('polls.urls')),
url(r'^admin/', include(admin.site.urls)),
)
- settings.py
DATABASES = {...
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': '/absolute/path/to/polls.db',-
INSTALLED_APPS = (
...,
'polls'
)
- polls.db (base de dades / database) (si va amb Apache, ha de pertànyer al grup www-data)
- polls
- test.py
- Models
- models.py
(model de la base de dades)
__unicode__: format en omissió, per text- s'hi poden afegir funcions:
was_published_today, ... short_description: títol que apareixerà si se'l referencia alist_display(admin.py)-
from django.db import models
import datetime
from django.utils import timezone
class Poll(models.Model):
question = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __unicode__(self):
return self.question
def was_published_today(self):
return self.pub_date.date() == datetime.date.today()
was_published_today.short_description = 'Published today?'
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
# to tune the admin display
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(max_length=200)
votes = models.IntegerField()
def __unicode__(self):
return self.choice - shell
python manage.py shellfrom polls.models import Poll, Choice- creació de Poll / creation of a Poll:
from django.utils import timezone
p = Poll(question="What's new?", pub_date=timezone.now())
p.save()
- creació de Choice / creation of Choice:
p = Poll.objects.get(pk=1)
p.choice_set.create(choice_text='Not much', votes=0)
p.choice_set.create(choice_text='The sky', votes=0)
- admin.py (gestió de la base de dades)
fields: ordre dels camps / field sortingadmin.site.register: fa que surti a la pàgina principal / make it appears at the main pagefieldsets: agrupacions de camps / groups of fieldsinlines: fa que surti a dins (relacionat via foreign key), en lloc del principi de tot / embedded instead of at the main pageadmin.StackedInline: llista / listadmin.TabularInline: taula, més compacta que la llista / table, more compact than list
list_display: selecció de camps que cal presentar a "Select poll to change" (si no, només mostrarà els especificats per__unicode__)- filtres / filters:
- cerques / searches:
- search_fields
- navegació per dates / navigation by date:
- date_hierarchy
- dependències / dependencies
- date_hierarchy
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)
- models.py
(model de la base de dades)
- 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'),
)
- opcionalment se li pot donar un nom (
- 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
- Text simple (text/plain):
- 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
- 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
- ...
- index.html
04Fitxers 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/
- put them on the directory specified by
- 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
- 1.6
- general (per site) static files:
- Only for production / integration with Apache or Nginx:
- copy them to
STATIC_ROOT(in flat structure), specified insettings.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 underdjcode_1.4) to Apache config file (Aliasmust 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>
- copy them to
- 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!" />
- 1.6: mysite/templates/myapp/toto.html
- 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;
}
- 1.6: mysite/polls/static/polls/css/style.css
- mysite/mysite/settings.py
- general config
05Media#
- 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):
# if not specified, defaults to FileSystemStorage
DEFAULT_FILE_STORAGE = ...
- non-default (models.py):
my_field = models.FileField( ... storage=OverwriteStorage() ...)
- default (settings.py):
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
- 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)
- settings.py
MEDIA_URL = '/mm/'
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()
- settings.py
- non-default:
- models.py
from s3utils import MyFirstS3Storage, MySecondS3Storage
class MyModel(models.Model):
my_field = models.FileField(storage=MyFirstS3Storage(), upload_to=...)
- models.py
- default (settings.py):
- bucket:
AWS_STORAGE_BUCKET_NAME = 'mybucket'
- root path:
AWS_LOCATION = "mylocation"
DEFAULT_S3_PATH = "media"
MEDIA_ROOT = '/%s/' % DEFAULT_S3_PATH
- bucket:
- 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)
- s3utils.py
- location
- s3utils.py
- @deconstructible
class NewMediaS3BotoStorage(S3Boto3Storage):
location='mylocation'
- @deconstructible
- s3utils.py
- bucket:
(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)
- s3utils.py
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- default (settings.py):
- 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 tomy_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
- djangosnippets
- Upload progress handler using cache framework (class UploadProgressCachedHandler) (678)
- Ajax progress bar (679)
- django-progressbarupload
- File
Upload Progress
- djangosnippets
- 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
- UnicodeEncodeError: 'ascii'
codec can't encode character
- Unicode in Python
- Solució / Solution
- UnicodeDecodeError
when
uploading file with non-english filename. #
comment 18
- /etc/apache2/envvars
## The locale used by some modules like mod_dav
#export LANG=C
## Uncomment the following line to use the system default locale instead:
. /etc/default/locale
- or use commands:
- /etc/apache2/envvars
- UnicodeDecodeError
when
uploading file with non-english filename. #
comment 18
- UnicodeEncodeError: 'ascii'
codec can't encode character
- 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)
- urls.py
- Producció / Production
- Desenvolupament / Development
- 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 nameclass Toto(models.Model)
toto_file = models.FileField(upload_to="toto_dir", storage=OverwriteStorage(), blank=True)
- models.py
- Overwrite File Storage System
- ImageField overwrite image file
- Bug 4339
/ 11663
(comment
#19)
- ...
06Vídeo / Video#
- Media
- Can I convert a django video upload from a form using ffmpeg before storing the video?
- How to run FFMPEG commands in Django?
- Calling
ffmpeg
doesn't work on apache server
- Popen does not work anymore
with apache/wsgi and python 2.7.2?
WSGIApplicationGroup %{GLOBAL}
- Popen does not work anymore
with apache/wsgi and python 2.7.2?
- Projectes relacionats / Related projects
- django-smartfields
- automatic conversion of images, videos (using ffmpeg)
- django-multimedia
- django-smartfields
07Imatges / Images#
- Projectes relacionats / Related projects:
- django-photologue
- django-imagekit
- ImageKit documentation (rtd)
- 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
- Division by zero
- Prevent div by zero for exif rationals with zero denominator #1492
- Solució / Solution
- use Pillow >= 3.1.0
- Division by zero
- Rotate
variations or keep exif info for JPEG #66
- 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
- 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
- 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' )
- myproject/settings.py
- sorl-thumbnail
- Requisits / Requirements
- Utilització / Usage
- models.py
class UploadImatge(models.Model)
media = models.ImageField(upload_to='ims')
- camins / paths
- relative (does not include MEDIA_ROOT):
imatge.media.name
- absolute (includes MEDIA_ROOT, if used storage is
FileSystemStorage or derived; when using S3BotoStorage,
it will be the same as imatge.media.name):
imatge.media.file.name
- relative (does not include MEDIA_ROOT):
- models.py
- Visualitza imatges / Display images:
- AdminImageWidget
- ManyToMany
- Option 1: define your own image_thumb field and use it
with ForeignKey (admin of Image: works; inline admin of
Mosaic: it works)(sí que es veu la imatge en petit a
l'inline; però una ImatgeDeFilm està lligada a un sol film i
no es pot reutilitzar) en lloc de ManyToMany (no es veu la
imatge a l'inline, perquè és un seleccionable simple):
- models.py
class Film(models.Model):
titol = models.CharField(max_length=100)
class ImatgeDeFilm(models.Model):
nom = models.CharField(max_length=100)
media = models.ImageField(upload_to='ims')
film = models.ForeignKey(Film)
def image_thumb(self):
return u'<img src="%s" width="100" height="100"/>' % self.media.url
image_thumb.short_description = 'Imatge en petitet'
image_thumb.allow_tags = True
def __unicode__(self):
return "%s (%s)" % (self.nom, self.media)
- admin.py
class ImatgeDeFilmInline(admin.TabularInline):
model = ImatgeDeFilm
readonly_fields = ['image_thumb']
class FilmAdmin(admin.ModelAdmin):
inlines = [ImatgeDeFilmInline]
admin.site.register(Film, FilmAdmin)
- models.py
- 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 = Trueclass 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)
- models.py
- 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:
08Generació 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)
- When
FieldTracker resets fields state
- 4.1.0
Breaking changes
- Solution
- no calls to tracker should be made after calling super() in overwritten save(); if you need them, set aux variables before calling super()
- post-save events
- upgrade to 4.2.0 (but not for
django 2, because other things are
broken with this combination) and
use context
@tracker(When FieldTracker resets fields state):import django
from contextlib import nullcontext
class MyModel(models.Model):
def save(self, *args, **kwargs):
# NOTE: in fact, it is not for django3 but for django-model-utils >= 4.2.0
# ideal case would be a decorator @tracker above def save, but it cannot be conditional to make it compatible wirh django-model-utils < 4.2.0
# https://stackoverflow.com/questions/27803059/conditional-with-statement-in-python
with self.tracker if django.VERSION[0] > 2 else nullcontext():
...class MyModel(models.Model):
@tracker
def save(self, *args, **kwargs):
- upgrade to 4.2.0 (but not for
django 2, because other things are
broken with this combination) and
use context
- The
relationship between FieldTracker and
post_save signal is not clearly documented
#283
changed_fields = instance.tracker.changed()
updated_values = {
field: getattr(instance, field) for field in changed_fields
}
- FieldTracker should patch save_base instead of save #404
- Undocument breaking changes in FieldTracker in Version 4.1 #491
- Solution
- 4.1.0
Breaking changes
- 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
...
- When
FieldTracker resets fields state
- 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):
# createMySecondModel
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)
- do something before the object is saved:
- 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
- admin.py
- 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',)
- models.py
- 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)
- models.py
- automatically create a list_display
field:
- serializers
- Django REST Framework: adding additional field to ModelSerializer
- add a new read-only field (can be
related to any other model)
class TotoSerializer(serializers.ModelSerializer):
...
new_field = serializers.SerializerMethodField('do_something')
def do_something(self, obj):
...
class Meta:
model = MyModel
fields = (
"model_field_1",
"model_field_2",
"new_field",
)
- add a new read/write field
- ...
- user from request instead of by pk
09Eclipse#
- 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
- using pyenv:
- ...
- using virtualenv:
- /opt/PYTHON27/bin/python
- using pyenv:
- 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
- select your project
- next times
- from top menu, select your entry from dropdown over debug or run icon
- first time
- 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)
- First time: select your project
- Alternative method: copy debug configuration from main
debug
- select your project
- Debug As: PyDev Django
- duplicate the Debug configuration to a new one and modify it
- select your project
- Alternative method: custom command
- select your project
- Django -> Custom command
- e.g.:
test --settings my_project.test_settings --verbosity 3 ...
- e.g.:
- Django -> Custom command
- modify
Debug configuration:
- Main
- Project:
myproject - Main Module:
${workspace_loc:wct_streaming_farm}/${DJANGO_MANAGE_LOCATION}
- Project:
- Arguments
- Program arguments:
test --settings my_project.tests_settings --verbosity 3 ...
- Working directory
- Default:
${project_loc:/selected project name}
- Default:
- Program arguments:
- ...
- Main
- select your project
- General method
- 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)
- debug response, even before reaching your code:
- Errors
- Undefined variable from import
- ...
- 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 *
- ...
- PyDev console -> Python console
10Bases de dades / Databases#
- Databases (1.8)
- Creació / Creation
-
first host (e.g. local server) second host (e.g. moving from local server to AWS Aurora)
hostname=127.0.0.1
su postgreshostname=...rds.amazonaws.comcreate user (using master_user)
and give it permissions to create databasesmaster_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.sqlpg_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}Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- only needed if not using
-
- Clear database
- Bolcat / Dump
- Configuració
de
la base de dades / Database configuration
-
mysite/settings.pyLinux 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'
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- AWS
RDS
- Create a PostgreSQL database from AWS console:
- Master user:
my_master_user - Database:
my_database
- Master user:
- 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 useruser_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 formy_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.
}
}
- setup_aws_rds.sh
- Create a PostgreSQL database from AWS console:
- 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
- So, What Could Go Wrong? (Answer: Replication Lag)
- Django
Db routing
- Possible solutions:
.using('default').- ...
- Possible solutions:
- 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
- my_project/test_settings.py
- TEST_MIRROR setting doesn't work as expected (and has no tests)
- Running
Tests When Replication Is Active
- settings.py
DATABASES = {
'replica_1': {
...
'TEST': {
'MIRROR': 'default',
}
}
}
- Option 2: force to use only one database (e.g.
'default'):
- Multi-database support
- test_...py
class MyAPITests(APITestCase):
databases = {'default'}
...
- Option 1: when testing replicas
- ...
- AWS Aurora
- Debug
- get instance:
User.objects.all()User.objects.using('replica_1').all()User.objects.using('replica_2').all()...
- save instance
my_user.save()my_user.save(using='default')my_user.save(using='replica_1')
- given an instance, get the database it is stored in:
myobj._state.db
- ...
- get instance:
- 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 syncdbpython manage.py migratepython manage.py loaddata dump.json- Problemes / Problems
- with original database (e.g. SQLite)
- 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
Django 1.6 / South
Django >=1.7
create database
syncdb
migratecreate migrations
schemamigration
makemigrations
apply migrations
migrate
migrate
show all migrations
showmigrationssquash migrations
squashmigrationsAquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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(...)
]
- temporary solution:
- Problemes / Problems
- Squash migrations
- steps:
- squash migrations, keeping old files
./manage.py migrate./manage.py showmigrations myapp1./manage.pysquashmigrationsmyapp1 xxxx- will create: myapp1/migrations/0001_squashed_xxxx_auto_yyyymmdd_hhmm.py
./manage.py migrate myapp- you may need to run:
./manage.py makemigrations myappif you are asked to (e.g. for models.DateTimeField(auto_now_add=True))
- you may need to run:
./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):
./manage migrate
- 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)
- delete all files it replaces (referenced in
- commit and release
- deploy changes to all your environments
- deploy files
- no need to run
./manage.py migrate
- squash migrations, keeping old files
- Problemes /
Problems
- the generated file gives a syntax error
- Improve the error message when squashing migration with RunPython that require manual changes
- Solució / Solution
- open the generated file and look at the
comments on top:
# Functions from the following migrations need manual copying.
# Move them and any dependencies into this file, then update the
# RunPython operations to refer to the local versions:
- open the generated file and look at the
comments on top:
- 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),
]
- 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
- Solució / Solution
- CircularDependencyError
- ...
- the generated file gives a syntax error
- steps:
- Django
>=1.7
migrations (django.db.migrations)
- Upgrading
from
South
- settings.py
INSTALLED_APPS = (
...
#'south',
)
cd migrationrm -f ????_* __init__.pycpython manage.py makemigrationspython manage.py migrate
- settings.py
- First time
- create database (for base Django):
python manage.py migrate
- start migrations (for applications):
python manage.py makemigrationspython manage.py migrate
- create a superuser:
python manage.py createsuperuser
- create database (for base Django):
- 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 my_app zero
- revert to status until migration 0007 (but no
further)
- 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
- for each application, reset its migrations:
- How
to Reset Migrations
- scenario 1: remove database
- remove and create database (PostgreSQL)
sudo su - postgresdatabase_name="..."dropdb ${database_name}- cre
atedb ${database_name}
- migrate
./manage.py migrate
- create admin user
./manage createsuperuser
- remove and create database (PostgreSQL)
- 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" -deletefind . -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
- update database:
- scenario 1: remove database
- to remove all migrations and start from scratch:
- remove the project database:
mysql -P 3307 -u root -pDROP DATABASE myprojectdb;
- create the database:
mysql -P 3307 -u root -pCREATE 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
- remove the project database:
- django-reset-migrations
- Upgrading
from
South
- 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 --autopythonmanage.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
pythonmanage.py convert_to_south toto_apppython manage.py schemamigration toto_app --initial
pythonmanage.py migrate toto_app 0001 --fake
- en altres ordinadors / in other computers (VCS
up to date)
pythonmanage.py migrate toto_app 0001 --fake
- en el propi ordinador / in own computer
- brand new app (no syncdb has been executed):
- Següents vegades / Next times
- modify your model
pythonmanage.py schemamigration my_app --autopythonmanage.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:
pythonmanage.py schemamigration my_app --auto --updatepythonmanage.py migrate my_app
- Llista de migracions / List migrations:
python manage.py migrate --list
- Instal·lació / Installation
- Evolution
-
- SQL
- SQLite
python manage.py dbshellsqlite> .help
- SQLite
- No SQL
- Django-nonrel
- django-nonrel (Github)
- MongoDB
- Using MongoDB with Django (IBM)
- Write a Tumblelog Application with Django MongoDB Engine
- Django admin site with mongodb (so)
- Modules
- PyMongo
- MonoEngine
- Django-nonrel
11Desplegament servidor HTTP / HTTP server deployment#
- How to deploy with WSGI
- Django documentation
- Deploying static files
- Cache
- Django’s cache framework
- Tipus / Types
-
type
CACHES BACKEND
Memory
'django.core.cache.backends.memcached.MemcachedCache' Database caching 'django.core.cache.backends.db.DatabaseCache' Filesystem caching
'django.core.cache.backends.filebased.FileBasedCache'
Local-memory caching
'django.core.cache.backends.locmem.LocMemCache'
Dummy caching (for development)
'django.core.cache.backends.dummy.DummyCache'
Custom
'path.to.backend'
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- 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(overwritten by max_age in cache_control)
CACHE_MIDDLEWARE_SECONDS
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 importcache_page
@cache_page(60*2)
def my_view(request, ...):
from rest_framework_extensions.cache.decorators import cache_response
class MyModelViewSet(viewsets.ModelViewSet):
@cache_response(settings.CACHE_TIMEOUT_MYMODEL_LISTVIEW, key_func=MyKeyConstructor())
def list(self, request, *args, **kwargs):
return viewsets.ModelViewSet.list(self, request, *args, **kwargs)
@cache_response(settings.CACHE_TIMEOUT_MYMODEL_MYDETAILVIEW, key_func=MyKeyConstructor())
@action(...)
def my_custom_view(self, request, *args, **kwargs)
...- NOTE: @cache_response does not
work with @api_view. Apply it to a
method (e.g. get()) in views.APIView instead
from rest_framework_extensions.cache.decorators import cache_response
class MyCustomAPIView(views.APIView):
@cache_response(settings.CACHE_TIMEOUT_MYCUSTOM_APIVIEW, key_func=MyKeyConstructor())
def get(self, request, *args, **kwargs):
...
cache only pieces
client downstream
- browser-based cache
- squid
HTTP cache headers
- Cache-Control
- views.py
from django.views.decorators.cache import cache_control
@cache_control(max_age=settings.CACHE_TIMEOUT_MYMODEL_MYDETAILVIEW)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- settings.py
-
- 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.pyfrom 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
- request header:
- 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):
...
- DRF-Extensions
- only for a specific view
- settings.py
- Django book
- MS Windows
- A Comparison of Web Servers for Python Based Web Applications
- Timeout
AWS load balancer nginx uwsgi AWS S3 storage config Description
- Idle timeout: 4000s
nginx.conf location / {
...
uwsgi_read_timeout 4000s;
}
/etc/uwsgi/vassals/myvassal.ini
[uwsgi]
...
# Set internal sockets timeout in seconds.
# to avoid error "timeout during read(65536) on wsgi.input"
socket-timeout = 300
s3utils.py
from storages.backends.s3boto3 import S3Boto3Storage
from django.utils.deconstruct import deconstructible
from botocore.config import Config
@deconstructible
class MyS3Storage(S3Boto3Storage):
# Possible solution to 504s on long-timed upload requests proposed by:
# https://github.com/jschneier/django-storages/issues/279#issuecomment-522063957
config = Config(
connect_timeout=4000,
read_timeout=4000,
retries={
'max_attempts': 20
}
)
raised error when
timeout is reachedAWS load balancer logs:
- 5...
django logs: 504 Gateway Timeout
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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=djangopid file /etc/sysconfig/celery /var/run/celery/beat.pid
log file /etc/sysconfig/celery
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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;
}
}
- ...
uwsgi
/usr/lib/systemd/system/emperor.uwsgi.service
[Unit]
Description=uWSGI Emperor
After=syslog.target
[Service]# yum install uwsgi uwsgi-plugin-python2ExecStart=/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
- ...
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
[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"
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- 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 -i /etc/uwsgi/emperor.iniuwsgi --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 usingsystem-installed uwsgi (yum installuwsgi-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:applicationdaphne -b 0.0.0.0 -p 9000 myproject.asgi:applicationdaphne -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 daphneserver {
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;...
}
}
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Option 1: Start the development
http server
cd djcode/mysitepython manage.py runserver [[0.0.0.0]:8080]
- Option 1.2: django-devserver
- https development server
- Security on a Django app – HTTPS everywhere
- django-sslserver
pip install django-sslserver- settings.py
INSTALLED_APPS = (...
"sslserver",
...
)
python manage.py runsslserver [192.168.1.15:8443] [--certificate /path/to/certificate.crt] [--key /path/to/key.key]
- 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
- Basic
configuration
- DJANGO_SETTINGS_MODULE
- Basic
configuration
- 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
libpythonX.Y.so ...
- 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 directoryof 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.confWSGIPythonPath /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.confListen 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-dataorapache(or whatever defined inGroupdirective in httpd.conf) as a secondary group of the user that owns the directory:# usermod -a -G www-data user_name
sudo chown www-data. -R polls- maybe changing only the group is enough:
- CentOS:
# chgrp apache service.db
- Ubuntu:
sudo chgrp www-data service.db
sudo chmod 664 service.db
- CentOS:
- add group
- if database is in a system
directory:
- (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-wsgicd /etc/apache2/mods-enabled; ln -s ../mods-available/wsgi.conf .; ln -s ../mods-available/wsgi.load .cd /etc/apache2/conf/sites-enabled; ln -s ../sites-available/django-site .
- CentOS
yum install mod_wsgi
- Mageia
- 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))DATABASE_NAME = "/absolute/path/to/toto.db"- IMPORTANT:
Apache group (
www-data, or whatever defined inGroupdirective in httpd.conf) must have write permission to this file and parent directories. - MEDIA_ROOT = "/absolute/path/to/media"
- make sure that your mod_wsgi version matches the
Python version:
- 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
- django_project/install_lamp/django_project.conf (or
use script create_apache_conf.sh)
- 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.pyno s'hi accedeixi mitjançant un enllaç simbòlic- Solució:
- ? afegir directiva FollowSymbolicLink
- Solució:
"unable to open database file"Error 500: Server error:(?) WSGIPythonPathneeds to be specified (on/etc/httpd/modules.d/B23_mod_wsgi.confor.../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 ...
- Solució / Solution:
- Check that WSGIPythonPath is correctly defined
- Multiple apps on Apache wsgi
- ImportError: No module named bootstrap_toolkit
- Solution:
- ...
- Solution:
- /var/log/httpd/error_log
Authentication credentials were not provided- Django: Rest Framework authenticate header
- Solució / Solution
- your_project.conf
WSGIPassAuthorization On
- your_project.conf
- Admin: redirect to login page (Sessions)
- solution:
- settings.py
- #SESSION_ENGINE =
'django.contrib.sessions.backends.cache'
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
- #SESSION_ENGINE =
'django.contrib.sessions.backends.cache'
- settings.py
- Django Admin Login - Redirecting back to login page
- solution:
- 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
- settings.py
- Solució
/ Solution
- CentOS:
Apache
- 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.rpmsudo dnf config-manager --enable gf-tesing
- install
sudo dnf install uwsgi uwsgi-plugin-python3
- activate GhettoForge
repo
- CentOS 7
sudo dnf install uwsgi uwsgi-plugin-python3
- CentOS/Alma 8
- des de paquets de la distribució / from
distribution packages
- 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
}
}
- /etc/nginx/conf.d/mysite_nginx.conf
- uWSGI config
- Nginx support
- Instal·lació / Installation
virtualenv /opt/p27source /opt/p27/bin/activatevirtualenv /path/to/mysite/envsource/path/to/mysite/env/bin/activatepip install uwsgi
- /etc/uwsgi/uwsgi_params
cd /etc/uwsgi/; wget https://raw.githubusercontent.com/nginx/nginx/master/conf/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/p27home = /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.inimysite2_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, ...)
- curl -i ...
- 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
- Resource temporarily unavailable using uwsgi + nginx
- get number of active
connections to the socket
netstat -pena --unix | grep uwsgi | wc
# default value: 128
sysctl -w net.core.somaxconn=2048- uwsgi.ini
# default value: 100
listen=2048
- /var/log/nginx/error.log
- Error
502
- /var/log/messages (or
journalctl -f)
uwsgi: -- unavailable modifier requested: 0 --
- Solució / Solution
- Things to know (best practices and “issues”) READ IT !!!
- when installing
uwsgi from distribution
packages:
- install also python
plugin
- CentOS
- Python 2
sudo yum install uwsgi-plugin-python2
- Python 3 (e.g.
3.6)
sudo yum install uwsgi-plugin-python36
- Python 2
- Alma 8
- Python 3.6
sudo dnf install uwsgi-plugin-python3
- to
install plugin
for a python
version
different from
3.6 (provided
by
uwsgi-plugin-python3),
e.g. 3.8:
- uwsgi_plugin_build-md
- passos /
steps
sudo -idnf install uwsgi-develdnf install redhat-rpm-config libcap-devel openssl-develcd /usr/src/uwsgi/2.0.21/plugins/PYTHON=python3.8 uwsgi --build-plugin "plugins/python python38"cp python38_plugin.so /usr/lib64/uwsgi/
- Python 3.6
- CentOS
- activate plugin in ini
conf file
(/etc/uwsgi/vassals/myvassal.ini):
[uwsgi]
# when usinguwsgi-plugin-python2
#plugins = python
# when usinguwsgi-plugin-python36
plugins = python36# when usinguwsgi-plugin-python3
plugins = python3
- install also python
plugin
- /var/log/messages (or
- 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 djangosudo /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 -iexport DJANGO_SETTINGS_MODULE=weblate.settings;/path/to/virtualenv/bin/uwsgi -i/etc/uwsgi/emperor.ini
- in weblate_uwsgi.ini,
use wsgi-file
instead of
module:
- Solution
OSError: DATA_DIR /home/myuser/weblate/weblate/../data is not writable!- Solutions
- Install
weblate in an
ad-hoc directory
- Filesystem
permissions
/var/lib/weblate
- Filesystem
permissions
- Install
weblate inside
home directory
(not
recommended)
- check that
there is a
user
djangoand that your user belongs todjangogroup:useradd djangousermod -a -G django myuser
chown django.django -R /home/myuser/weblate/datachmod g+ws/home/myuser/weblate/data
- check that
there is a
user
- Install
weblate in an
ad-hoc directory
- Solutions
- 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
- /var/log/messages
- directly
- 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",
)
- res = self.client.post(
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]
- check dir permissions
502 Bad Gateway
uwsgi: -- unavailable modifier requested: 0 --
- use python plugin
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
- listen=2048
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)
- add swap memory
- (?) AWS_S3_MAX_MEMORY_SIZE
- only used by open()?
...
[Errno 12] Cannot allocate memoryNginx, unable to fork: Cannot allocate memory - myvassal.ini
- # respawn worker after 100
requests
max_requests=100 - limit-as=1024
- # respawn worker after 100
requests
- 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
}
)
- @deconstructible
- nginx.conf
... (504?)
timeout during read(65536) on wsgi.input
-
Timeout
- myvassal.ini
- [uwsgi]
socket-timeout = 300
- [uwsgi]
- myvassal.ini
... 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;
- # when a client closes the
connection then keep the channel to
uwsgi open. Otherwise uwsgi throws
an IOError
- myvassal.ini
- ignore-sigpipe = true
ignore-write-errors = true
disable-write-exception = true
- ignore-sigpipe = true
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- views.py
- Memory leak
- The uWSGI Stats Server
- setup
- /etc/uwsgi/emperor.ini
[uwsgi]
...
stats = :3030
- /etc/uwsgi/vassals/my_vassal_1.ini
[uwsgi]
...
master = true
# needed to see RSS, VSS in uwsgitop
memory-report = true
stats = :3031
- /etc/uwsgi/vassals/my_vassal_2.ini
[uwsgi]
...
master = true
memory-report = true
stats = :3032
- /etc/uwsgi/emperor.ini
- get the statistics from my_vassal_1 (one
of the following):
uwsgitop :3031uwsgi --connect-and-read :3031
- setup
- Things to know (best practices and “issues”) READ IT !!!
- Why should I use max-request to avoid memory leaks?
- Django memory leak: possible causes?
- django memory leaks, part I
- django memory leaks, part II
- The uWSGI Stats Server
[Errno 12] Cannot allocate memory- Nginx, unable to fork: Cannot allocate memory
- Solució / Solution
- use
max-requests, to respawn the worker after the specified number of requests (see uwsgitop)- /etc/uwsgi/vassals/my_vassal_1.ini
[uwsgi]
...
max-requests = 100
- /etc/uwsgi/vassals/my_vassal_1.ini
- add swap memory (EC2 swap)
- add the following entry to uwsgi conf file
(to limit the address space to 1024MB):
limit-as = 1024
- use
- django logs:
timeout during read(65536) on wsgi.input- Solució / Solution
- django logs:
Internal Server Error: ...
UnreadablePostError at ...
errorduring read(65536) on wsgi.input- Solució / Solution
- ...
- Solució / Solution
- /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)
- Solució / Solution:
- /var/log/messages:
SIGPIPE: writing to a closed pipe/socket/fd (probably the client disconnected) on request ...
...
uwsgi: IOError: write error
-
- Gunicorn
- uWSGI
12Settings#
- Ú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
- settings.py
# DEBUG = True
# TEMPLATE_DEBUG = DEBUG
ALLOWED_HOSTS = ['*']
- fitxers estàtics / static files
- fitxers mèdia / media files
- loggers
- settings.py
- Diversos fitxers de configuració / Several setting files
- Contrasenyes (per a no
tenir-les al git) / Passwords (to avoid having them in git)
- Generació
de contrasenyes amb openssl / Password generation with
openssl
- settings.py
'MY_PASSWORD':open(os.path.join(BASE_DIR, 'mypassword.txt'),'r').read().strip(),
- Generació
de contrasenyes amb openssl / Password generation with
openssl
13Timezone#
- 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 Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Dates are stored in UTC into the database
- settings.TIME_ZONE is used for user interaction (forms, list_display, ...)
14CORS#
- 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",
)
15Plantilles / Templates#
- Templates (1.6)
- Templates in admin
- The
Django
template language
- Elements
- variable:
{{ original.my_field }}
{{ variable|filter }}- convert a string to int:
{{ my_int|slugify }}- exemple:
{% if my_model.my_int|slugify == some_other_int_as_string_from_context %}{% endif %}
- tag:
{% tag %}...{% end_tag %}if, for, ...
- comment:
{# my_comment #}{% comment %}...my_comment...{% endcomment %}
- custom
- variable:
- Template
inheritance
- my_project/my_app/templates/my_app/view1.html
- my_project/my_app/templates/admin/my_app/my_change_list_template.html
class TotoAdmin(admin.ModelAdmin)
change_list_template = 'admin/my_app/my_change_list_template.html'
...
- my_project/templates/
- base.html
- [my_app/]
- view1.html
- Exemples / Examples
- Personalitza l'aspecte de l'admin
- Personalitza l'aspecte de l'admin
- Elements
- Table rowspan
- Static files (css, js, ...)
- Settings
- Using relative paths for Django template directories
- Integració amb altres / Integration with other HTML templates
- Not Another Django Blog! - Part 2 (Initializr, Boilerplate)
16Forms#
- Forms (API reference)
- Working
with
forms
- Exemples / Examples
- Formsets
- Creating
forms
from models
- models.py
from django.forms import ModelForm
class Toto(models.Model):
...
class TotoForm(ModelForm)
class Meta:
model = Toto
- models.py
- Form Media (CSS, Javascript) (see also static files)
17Seguretat / Security#
- Contrasenyes / Passwords
- Saving
passwords for other services (semi-)securely in a database
# provides Crypto
pip install pycryptodome
- Saving
passwords for other services (semi-)securely in a database
- CSRF (Cross Site Request Forgery)
- CSRF (Security tips for web developers)
- Cross
Site Request Forgery protection (Django)
.../lib/python2.7/site-packages/django/middleware/csrf.py (line 161)REASON_BAD_REFERER
- Working
with AJAX, CSRF & CORS (DRF)
- Javascript clients
- "AJAX requests that are made within the same context as the API they are interacting with will typically use SessionAuthentication"
- "AJAX requests that are made on a different site from the API they are communicating with will typically need to use a non-session-based authentication scheme, such as TokenAuthentication."
- Javascript clients
- Issue with CsrfViewMiddleware and "referer" same_origin checking for secure (https) subdomains #2982
- CSRF verification Failed - Referer is insecure while host is secure
- Forbidden
403 status with csrf check (REASON_BAD_REFERER) via HTTPS
#839
{"detail": "CSRF Failed: Referer checking failed - http://localhost:8800/jstest/ does not match https://my.api.server.com/."}- tomchristie
commented on 16 May 2013
- "Session authentication is only appropriate for
javascript based clients that are running in the
same site as the API.
Other clients such as python based client, and javascript clients on a different site that interact with the API should use a standard token-based auth, such as OAuth2."
- "Session authentication is only appropriate for
javascript based clients that are running in the
same site as the API.
- Solució / Solution
- check that SessionAuthentication is not present in settings.py. Only TokenAuthentication.
18Usuaris / 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)
python manage.py createsuperuser- how to reset django admin
password?
python manage.py changepassword admin
- Django tips: extending the User model
- Autenticació
d'usuari
/ User authentication
- Autenticació de servidors / Server authentication
- api-key
- 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': (...)
- 'rest_framework.authentication.SessionAuthentication'
- 'rest_framework.authentication.BasicAuthentication'
'DEFAULT_PERMISSION_CLASSES': (...)
- 'rest_framework.permissions.AllowAny'
- 'rest_framework.permissions.IsAuthenticated'
- 'rest_framework.permissions.IsAuthenticatedOrReadOnly'
- 'rest_framework.permissions.IsAdminUser'
- 'rest_framework.permissions.DjangoModelPermissions'
- 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
- 'rest_framework.permissions.DjangoObjectPermissions'
- Custom permissions
'DEFAULT_AUTHENTICATION_CLASSES': (...)
- 'rest_framework_jwt.authentication.JSONWebTokenAuthentication'
'DEFAULT_AUTHENTICATION_CLASSES': (...) - 'rest_framework_simplejwt.authentication.JWTAuthentication',
'DEFAULT_AUTHENTICATION_CLASSES': (...)
- 'rest_framework.authentication.TokenAuthentication',
(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
- ...
- SOCIALACCOUNT_*
- SOCIALACCOUNT_PROVIDERS = {...}
- ...
- 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
- default values:
-
key
default value
settings
login_url '/accounts/login/' LOGIN_URL login_redirect_url
'/accounts/profile/'
LOGIN_REDIRECT_URL
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- @login_required
- @login_required(login_url='/accounts/login/')
- @user_passes_test()
- @permission_required()
django.contrib.auth.decorators
@login_required(login_url="/accounts/login/")
@verified_email_required
from rest_framework.decorators import detail_route
class MyModelViewSet(viewsets.ModelViewSet):
...
@detail_route(permission_classes=[IsAdminOrIsSelf])
def my_function(self, request, **kwargs):
authentication views in
views.py
Profile view
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
from rest_auth.registration.views import SocialLoginView
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
...
urls.py / routing.py
- url('^', include('django.contrib.auth.urls'))
url(r'^accounts/', include('allauth.urls')),
url(r'^accounts/profile/', YOUR_OWN_PROFILE_VIEW),
from channels.auth import AuthMiddlewareStack
application = ProtocolTypeRouter({
"websocket": AuthMiddlewareStack(
URLRouter([
url(r"^front(end)/$", consumers.AsyncChatConsumer),
])
),
})
- url(r'^api-token-auth/', 'rest_framework_jwt.views.obtain_jwt_token')
- url(r'^api-token-verify/', 'rest_framework_jwt.views.verify_jwt_token')
- url(r'^api-token-refresh/', 'rest_framework_jwt.views.refresh_jwt_token')
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView,
)
urlpatterns = [
...
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
...
]url(r'^rest-auth/', include('rest_auth.urls'))
url(r'^rest-auth/', include('dj_rest_auth.urls'))
# needed by rest-auth/password/reset/
# https://github.com/Tivix/django-rest-auth/issues/63
url(r'^', include('django.contrib.auth.urls')),
(r'^rest-auth/registration/', include('rest_auth.registration.urls'))
(r'^rest-auth/registration/', include('dj_rest_auth.registration.urls'))
url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login')
...
templates
- registration/login.html
- ...
entry points
http://127.0.0.1:8000 - /login
- /logout
- /password_change
- /password_reset
- /password_reset/done
- /reset
- /reset/done
http://127.0.0.1:8000/accounts - /signup
- /login
- /logout
- /password/change
- /password/set
- /inactive
- /confirm-email
- /password/reset
- /password/reset/done
- /password/reset/key
- /password/reset/key/done
http://127.0.0.1:8000/accounts
- /social
- ...
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
-H "Authorization: JWT ..."
- 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:
-H "Authorization: Bearer ${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:
-H "Authorization: Token ..."
- /user gives information about the current user
- Update
UserProfile
- USER_DETAILS_SERIALIZER
- token is stored in database
- registration of new users
- implements a user viewset for you
authentication
authentication
social authentication
authentication
authentication
registration
social authentication
default
django-allauth
(default)
djangorestframework-jwt djangorestframework-simplejwt django-rest-auth
Django
django-rest-framework authentication Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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):
...
- settings.py
- 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'
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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
- for authentication in
self.authenticators
(BasicAuthentication,
TokenAuthentication,
...)
- self._authenticate
- ...
- rest_framework.request.user
- self.check_permissions
- self.check_throttles
- self.perform_authentication
- 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()
- ...
- django.contrib.auth.authenticate()
(for backend: user =
_authenticate_with_backend(backend,
...), # list is defined
by
- rest_auth.serializers.LoginSerializer.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)
- rest_auth.utils.default_create_token()
- self.process_login()
- django_login()
- django.contrib.auth.login()
- request.session.cycle_key()
- SessionStore.create()
- request.session.cycle_key()
- django.contrib.auth.login()
- django_login()
- get_response()
- ...
- serializer.is_valid()
- dj_rest_auth.views.LoginView.post()
- self.initial
- ...
- rest_framework.views.APIView.dispatch
- protected view: GET, POST ... -H
'Authorization: Token xxx' /my/endpoint
- rest_framework.views.APIView.dispatch
- self.initial
- ...
- response = handler(request, ...)
- ...my view ...
- ...
- self.initial
- ...
- rest_framework.views.APIView.dispatch
- ...
-
- Creating superusers
- Multi-site
- Protecció addicional / Additional protection
- Validació de la contrasenya / Password
validation
- Password
validation (Django)
AUTH_PASSWORD_VALIDATORS
- How to Create Custom Password Validators in Django
- Password
validation (Django)
- 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
- Integration with django-allauth (v4.5.4)
- Integration with Django Allauth (v5)
- ...
- 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
- myproject/settings.py
- with django restframework
- ...
- 5.x: Python 3, Django 2
- settings.py
- ...
- with allauth
- with django restframework
- settings.py
- 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'
- settings.py
- Solució / Solution
class AppConf(metaclass=AppConfMetaClass):
^
SyntaxError: invalid syntax
- Solució / Solution
- latest django-appconf version compatible with Python 2.7 is 1.0.3
- Downgrade django-appconf
to 1.0.3
pip install django-appconf==1.0.3
- Solució / Solution
- 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
- 4.5.4: Python 2.7
- django-axes
- Validació de la contrasenya / Password
validation
- 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
- Storing
additional
information about users
(1.4)
- AUTH_PROFILE_MODULE
- Storing
additional
information about users
(1.4)
- 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_savefrom 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)
- Properly
Admin’ing
User Profile Models
- use case 2: authentication must be
done using a field different from
"username"
USERNAME_FIELD = 'email'
- use case 3: authentication must be
done using a new field ('dni')
- Django < 1.5
- Extending
the
existing User model
(before 1.5)
- OneToOneField(User)
- AUTH_USER_MODEL
- Extending
the
existing User model
(before 1.5)
- Django >= 1.5
- Substituting
a
custom User model (new in
1.5)
- AbstractBaseUser
- admin has to be defined
- AbstractUser
- uses django.contrib.auth.admin.UserAdmin
- useful for just adding some fields (needed for authentication)
- AbstractBaseUser
- Substituting
a
custom User model (new in
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)
- Using
Django auth UserAdmin for a
custom user model
from django.contrib.auth.admin import UserAdmin
class MyUserAdmin(UserAdmin):
model = MyUser
fieldsets = UserAdmin.fieldsets + (
(None, {'fields': ('dni',)}),
)
admin.site.register(MyUser, MyUserAdmin)- A full example (1.5 documentation)
- Changing password in Django Admin
- Using
Django auth UserAdmin for a
custom user model
- Django < 1.5
- use case 1: additional fields, not
used to authenticate (profiles)
- Use cases
- Extending
the
existing User model / Substituting
a
custom User model
- views.login
- Curso Django: gestión de usuarios
- Authenticating against Django’s user database from Apache
- Sessions
- Multi-/Two-factor authentication
(MFA)
- “Adding
Two-Factor Authentication to Django (with
django-allauth)” Lightning Talk
- django-allauth
- native MFA support from version 0.56.0
- dj-rest-auth
- django-allauth-2fa
- replaced by native support from MFA in django-allauth
- Paquets / Packages
- django-otp
- django-two-factor-auth
- Yubico
- Admin
- Django allauth
- django-allauth-2fa
- Multifactor Authentication on Django Rest
- “Adding
Two-Factor Authentication to Django (with
django-allauth)” Lightning Talk
- External authentication (other authentication sources)
- SAML2
- Info
- SAML 2.0: Technical Overview (yt)
- Code
Your Python App to Provide SSO via
OneLogin
- python3-saml
- Paquets / Packages
- Info
- SAML2
- Social authentication
- Signing up and signing in
- What's the best solution for OpenID with Django? [closed]
- django-allauth
- Readthedocs
- Can be used as:
- extension for default authentication
- social authentication
- SOCIALACCOUNT_*
- Django-allauth Tutorial
- The missing django-allauth tutorial
- Signing Up and Signing In: Users in Django with Django-AllAuth
- Advanced
usage
- MFA
- Multi-Factor Authentication
- from version 0.56.0
- 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/
- lib/python2.7/site-packages/allauth/templates/account/email/
- 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/
- when using django-rest-auth,
this page must send a POST to API
REST server. Curl equivalent:
- my_project/settings.py
- Custom reset password email message
- allauth/account/forms.py
- ResetPasswordForm
- allauth/account/forms.py
- 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
- pip
install "dj-rest-auth==0.1.1"
- 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
- my_app/serializers.py
- How
can I update UserProfile assigned to
User model?
- Installation
- pip install django-rest-auth
- Angular
- 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/ )
- do not use Content-Type:
multipart/form-data:
- Identity providers
- Facebook
- create an application
- Configure your Django with given App
ID and App Secret:
- http://localhost:8000/admin/socialaccount/socialapp/add/
- Provider: Facebook
- Client id: <your App ID>
- Secret key: <your App Secret>
- Chosen sites: ...
- http://localhost:8000/admin/socialaccount/socialapp/add/
- try it:
- http://localhost:8000/accounts/login/
- Note: http://127.0.0.1:8000/accounts/login/ will not work
- Facebook
- Autenticació de servidors / Server authentication
- 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'Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- ViewSet
class ParticipantChallengeViewSet(viewsets.ModelViewSet):...
permission_classes = (permissions.IsAuthenticated,)
- settings.py
- 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',)
# onlyobjects 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
defget_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)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- 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 RequestContextdef 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}}
- nou_usuari.html
- urls.py
- 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/
- registration/
- si funciona, anirà a accounts/profile (LOGIN_REDIRECT_URL)
- urls.py (using
- 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
- urls.py (using
19Admin#
- 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)
- my_project/urls.py
- 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 import forms
from django.db import models
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.TextField: {"widget": forms.Textarea(attrs={"rows": "1"}}
}
from django.contrib.gis.db import models
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.TextField: {"widget": widgets.Textarea(attrs={"rows": "1"})},
}
- mysite/myapp/admin.py
- 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;
}
- default
- ...
- classes
- 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 = Truelist_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
- Custom
- list_display
- 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']}),
]
- no groups
- 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,]
- admin.py
- Link to modify object
show_change_link = True
- How to limit django admin inline formsets
- How do I require an inline
in the Django Admin?
- forms.py
class RequiredInlineFormset(BaseInlineFormSet)
...
- admin.py
class TotoInline(admin.StackedInline)
...
formset = RequiredInlineFormSet
- forms.py
- 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:
- Allow to
overwrite get_inline_instances
#16 (pull request)
- <virtualenv_path>/src/django-nested-inline/nested_inline/admin.py
- <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...")
- ??
- Allow to
overwrite get_inline_instances
#16 (pull request)
- not working with validate_unique
- in order to use overwritten
get_inline_instances (see above: conditional
inlines), the following files has to
be modified:
- django-nested-inline
- Multiple fk
- when several fk exist from inlined object to
present one, you must choose one:
fk_name = '...'
- 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
- Simple examples
- Groups
- 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
- deletion of a single instance: use ModelAdmin.delete_model(self, request, obj)
- bulk deletion (default action in list display): delete_selected
- models.py
- Overriding
predefined model methods
- 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
- Gràfiques / Charts
- Informes / Reports
- 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')),
...
- my_project/my_project/settings.py
- Usage
- Action: Massively edit
- Instal·lació / Installation
- django-mass-edit
- django-adminplus
- Instal·lació / Installation
pip install django-adminplus
pip install -e git://github.com/jsocol/django-adminplus#egg=django-adminplus
- Utilització / Usage
- my_project/my_project/settings.py
INSTALLED_APPS = (
#'django.contrib.admin',
'django.contrib.admin.apps.SimpleAdminConfig',
'adminplus',
)
- my_project/urls.py
from adminplus.sites import AdminSitePlus
admin.site = AdminSitePlus()
admin.autodiscover()
- my_project/my_app/admin.py
@admin.site.register_view('my_view',_("View to do something"))
def my_view(request, *args, **kwargs):
# do something
# redirect to admin home
url = reverse('admin:index')
return HttpResponseRedirect(url)
- my_project/my_project/settings.py
- 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 }}
- modify file
/your_python_path_or_virtualenv//src/django-adminplus/adminplus/templates/adminplus/base.html
- Solució / Solution:
- Instal·lació / Installation
- Grappelli
- django-grappelli (github)
- 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"
- my_project/urls.py
- Custom ordering
- Overriding
admin
templates
- Customizing your project’s templates (Tutorial 7)
- Customizing your application’s templates (Tutorial 7)
- AdminSite attributes (dev version)
- Customizing the Django Admin (slides)
- Reverse admin
- admin:...
- Extra
context (e.g. a url for a button in a detail page)
- admin.py
class MyModelAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, form_url="", extra_context=None):
# https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.changelist_view
extra_context = extra_context or {}
# complete context with my_url
device_stats_url = ""
obj = self.get_object(request, object_id)
extra_context["my_url"] = obj...
return super().change_view(
request,
object_id,
form_url,
extra_context=extra_context,
)
- my_project/my_app/templates/admin/my_app/my_model/change_form.html
{% extends "admin/change_form.html" %}
{% load i18n %}
{% block object-tools-items %}
<li><a href="{{ my_url }}">{% trans "Go to url button" %}</a></li>
{{ block.super }}
{% endblock %}
- admin.py
- 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
- object-tools
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)
- object-tools
- ...
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 %}
ModelAdmin
admin/delete_confirmation.html
classMyModelAdmin(admin.ModelAdmin):
# no need to specify if you put html template file in the canonical dir
delete_confirmation_template = "admin/my_app/my_model/delete_confirmation.html"
Action
- django.contrib.admin.actions.delete_selected()
admin/delete_selected_confirmation.html
-
classMyModelAdmin(admin.ModelAdmin):
# ? default:
delete_selected_confirmation_template = "admin/delete_selected_confirmation.html"# no need to specify if you put html template file in the canonical dir
delete_selected_confirmation_template = "admin/my_app/my_model/delete_selected_confirmation.html"
fieldset
field inline
admin/edit_inline/stacked.html -
admin/edit_inline/tabular.html -
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- ...
- 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_textinline 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
...
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- add block "description" to your project-wide
template
my_project/templates/admin/change_list.html
- 5
ways to make Django Admin safer
- Change the URL
- Visually distinguish environments
mkdir -pmy_project/templates/admincp -pr .../lib/python2.7/site-packages/django/contrib/admin/templates/admin/base_site.htmlmy_project/templates/adminmy_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.pyfrom 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.pyDEPLOYMENT_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 -pmy_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
- TEMPLATE_DIRS
- ...
cas / case access url view template
class MyModelAdmin(admin.ModelAdmin):from django.urls import path, re_path
class MyModelAdmin(admin.ModelAdmin):
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 = [
...
]
return my_urls + urlsclass MyModelAdmin(admin.MmodelAdmin):
general button
(top right in list page)
# modified change_list.html should include general buttonschange_list.html
change_list_template = "admin/my_app/my_model/change_list.html"
...
my_urls = [
re_path(
# general button
# /admin/my_app/my_model/my_general_button/
r"^my_general_button/$",
self.admin_site.admin_view(self.my_general_button_view),
# my_app_my_model_my_general_button
name="%s_%s_my_general_button" % info,
),
]
def my_general_button_view(self, request, *args, **kwargs):
context = {}
return TemplateResponse(
request,
"admin/my_app/my_model/my_general_button.html",
context,
)
general button
(top right in detail page)...
my_app/templates/admin/my_app/my_model/change_form.html
{% extends "admin/change_form.html" %}
{% load i18n %}
{% block object-tools-items %}
<li><a title="Mixers" href={% url 'admin:my_app_my_model_changelist' %}?my_model__id__exact={{ object_id }}>{% trans "Mixers" %}</a></li>
{{ block.super }}
{% endblock %}
general button
(top right in detail page)
to an intermediate page
(see section ... below)...
row button
(each row in list page)list_display = (
"my_row_button",
)
def my_row_button(self, item):
result = format_html(
'<a class="button" href="{}">My text</a>',
reverse("admin:my_app_my_model_my_row_button", args=[item.uuid])
)my_urls = [
re_path(
# row button
# /admin/my_app/my_model/{uuid}/my_row_button/
r"^(?P<uuid>.+)/my_row_button/$",
self.admin_site.admin_view(self.my_row_button_view),
# my_app_my_model_my_row_button
name="%s_%s_my_row_button" % info,
),
]
def my_row_button_view(self, request, uuid, *args, **kwargs):
my_instance = self.get_object(request, uuid, from_field="uuid")
context = {}
context["my_field_1"] = ...
return TemplateResponse(
request,
"admin/my_app/my_model/my_row_button_template.html",
context,
)
field button
action to a generic page
(without using a view)actions = [
"my_page_action_from_admin"
]
def my_page_action_from_admin(self, request, queryset):
context = {}
context["items"] = queryset
return TemplateResponse(
request,
"admin/my_app/my_model/my_page_action_template.html"
context
)
my_page_action_from_admin.short_description = _("Show a page without using a view")
action to a view
(can be called directly with url params)
related to model
actions = [
"my_view_action_from_admin"
]
def my_view_action_from_admin(self, request, queryset):
pk_list = queryset.values_list("pk", flat=True)
object_ids = ",".join(str(s) for s in pk_list)
redirect_url = reverse(
"admin:my_app_my_model_my_view_action_view",
args=[object_ids],
)
return HttpResponseRedirect(redirect_url)
my_view_action_from_admin.short_description = _("Show a view with selected items")
path(
"toto/<str:object_ids>/",
self.admin_site.admin_view(self.my_view_action_view),
name="my_app_my_model_my_view_action_view",
),
def my_view_action_view(self, request, object_ids, *args, **kwargs):
# convert comma-separated str to list
object_ids_list = object_ids.split(",")
queryset = self.get_queryset(request)
objects = queryset.filter(pk__in=object_ids_list)
context = {"objects": objects}
return TemplateResponse(
request,
"admin/my_app/my_model/my_view_action_template.html",
context,
)
my_app/templates/admin/my_app/my_model/my_view_action_template.html
{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls static %}
{% block extrahead %}
{{ block.super }}
{{ media }}
{% endblock %}
{% block content %}
<ul>
{% for obj in objects %}
<li>{{ obj }}</li>
{% endfor %}
</ul>
{% endblock %}
action to a view (ids in url params)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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 = TrueAquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- 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")
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- 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 importparse_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,)
defget_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 %}
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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 BytesIOfrom 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,)
defget_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)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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:
- 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"
# 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)
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 %}
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- ...
- 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 = [
re_path(
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,
),
re_path(
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
object_id =kwargs["pk"]
instance = self.get_object(request, object_id)
# 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)
# back to object detail
url = reverse(
"admin:%s_%s_change" % self.get_model_info(),
current_app=self.admin_site.name,
args=[object_id],
)
return HttpResponseRedirect(url)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Afegir un botó que porti a una altra part de
l'admin (llista dels altres objected que estan
relacionats amb el meu via FK)
- En el camp
- camp editable
- admin.py
from django.contrib import admin
from django.db import models
from .widgets import ButtonWidget # El teu widget personalitzat
class MyModelAdmin(admin.ModelAdmin):
# for all fields of type CharField
#formfield_overrides = {
# models.CharField: {'widget': ButtonWidget},
#}
# for specific field
def formfield_for_dbfield(self, db_field, request, **kwargs):
"""
Hook to selectively override the widget for a specific field name.
"""
if db_field.name == "my_field_with_button":
kwargs["widget"] = ButtonWidget
return super().formfield_for_dbfield(db_field, request, **kwargs)
- widgets.py
from django.forms import widgets
from django.utils.safestring import mark_safe
class ButtonWidget(widgets.TextInput):
def render(self, name, value, attrs=None, renderer=None):
# Genera el camp de text estàndard
html = super().render(name, value, attrs, renderer)
# Afegeix el botó al costat
# button_html = (
# f'<button type="button" onclick="alert(\'Acció!\')">Fes clic</button>'
# )
button_html = format_html(
'<a class="button" href="{}">Som-hi</a>',
value,
)
return mark_safe(
f'<div style="display:flex; gap:10px;">{button_html}{html}</div>'
)
- camp no editable (ignora
formfield_overrides) - admin.py
from django.contrib import admin
from django.db import models
from .widgets import ButtonWidget # El teu widget personalitzat
class MyModelAdmin(admin.ModelAdmin):
def url_with_button_field(self, obj):
# Renderitzem manualment el widget o l'HTML directament
widget = ButtonReadonlyWidget()
html = widget.render("url", obj.url)
return mark_safe(html)
url_with_button_field.short_description = "Etiqueta del camp"
fields = (readonly_fields = (
# "url",
"url_with_button_field"
)
# "url",
"url_with_button_field"
)- widgets.py
from django.forms import widgetsfrom django.utils.html import format_html
from django.utils.safestring import mark_safe
class ButtonReadonlyWidget(widgets.TextInput):
def render(self, name, value, attrs=None, renderer=None):
# Genera el camp de text estàndard
html = value
# Afegeix el botó al costat
button_html = format_html(
'<a class="button" href="{}">Som-hi</a>',
value,
)
return mark_safe(
f'<div style="display:flex; gap:10px;">{button_html}{html}</div>'
)- camp no editable amb un botó cap a un reproductor
- mydir/
- mysite/
- admin.py
from django.contrib import admin
from django.urls import path
from django.template.response import TemplateResponse
class MyAdminSite(admin.AdminSite):
def player_url_view(self, request, media_src, *args, **kwargs):
# html
context = {}
context["media_src"] = media_src
return TemplateResponse(
request,
"admin/player_url_template.html",
context,
)
def get_urls(self):
custom_urls = [
path(
"player_url/<path:media_src>/",
self.admin_view(self.player_url_view),
name="my_player_url",
),
]
return custom_urls- widgets.py
from django.forms import widgets
from django.utils.safestring import mark_safe
from django.utils.html import format_html
from django.urls import reverse
class ButtonReadonlyWidget(widgets.TextInput):
def render(self, name, value, attrs=None, renderer=None):
# standard readonly text
html = value
# add player button
button_html = format_html(
'<a class="button" href="{}">Player</a>',
reverse("myadmin:my_player_url", args=(value,)),
)
return mark_safe(
f'<div style="display:flex; gap:10px;">{button_html}{html}</div>'
)- urls.py
from .admin import MyAdminSite
my_admin_site = MyAdminSite(name="myadmin")
urlpatterns += [
path("admin/", admin.site.urls),
path("admin/", my_admin_site.urls),
]- myapp/
- admin.py
from mysite.widgets import ButtonReadonlyWidget
class MyModelAdmin(admin.ModelAdmin):
def url_with_button(self, obj):
widget = ButtonReadonlyWidget()
html = widget.render("url", obj.url)
return mark_safe(html)
url_with_button.short_description = "URL with button"
readonly_fields = (
# "url",
"url_with_button",
)
fields = (
# "url",
"url_with_button",
)- templates/
- player_url_template.html
... {{ media_src }}
- Element de la llista / Element in a list
- 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 Filefrom 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 importexport_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")
- actions.py
- Django Admin - change header 'Django administration' text
- Gràfiques
- AdminSite
(1.7)
- 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")
- models.py
- 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 importMyModelSerializer
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")
- actions.py
- 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
defcommon_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]
- models.py
- 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 simple / Simple 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 every element of 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 | unlocalize }}" />
{% endfor %}
</ul>
<input type="checkbox" name="dry_run" value="True" checked>
<label for="dry_run"> Dry run</label><br>
<!-- 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 %}
- my_app/admin.py
- Example with a list of checkboxes
- 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.
# get list of selected checkboxes
selected_my_items = request.POST.getlist("selected_my_items")
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
my_items = [...]
context = {"queryset": queryset, "my_items": my_items}
return render(request, "admin/my_app/my_action_multiple_selection_template.html", context)
my_action.short_description = _("Perform action on selected items")
- my_app/templates/admin/my_app/my_action_multiple_selection_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>
Select:
<ul>
{% for my_item in my_items %}
<li>
<!-- variable selected_my_items will contain multiple values, but it must be read withrequest.POST.getlist("selected_my_items")-->
<input type="checkbox" id={{ my_item.pk|unlocalize }} name="selected_my_items" value={{ my_item.pk|unlocalize }}>
<label for={{ my_item.pk|unlocalize }}>{{ my_item }}</label><br> </li>
{% endfor %}
</ul>
to be applied to:
<ul>
{% for obj in queryset %}
<li>{{ obj }}</li>
<!-- the following line must be present in every element of 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 | unlocalize }}" />
{% 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 %}
- my_app/admin.py
- 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'
- ...py
- 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]
- urls.py
- 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
- my_app/admin.py
- 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
- Example dealing with exceptions:
- 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 objectsdef 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
defget_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 requestdef 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)
- admin.py
- 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()]
...
- admin.py
- View
all log entries in the admin 2
- 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)
- models.py
- Horizontal or vertical interface (two boxes)
class MyModelAdmin(admin.ModelAdmin)
filter_horizontal = ('field_1',)class MyModelAdmin(admin.ModelAdmin)
filter_vertical = ('field_2',)- Improve Performance in Admin ManyToMany
- 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)
- models.py
- 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)
- models.py
- 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)
- option 1: use through
(every m2m relation has it, even if not explicitly
declared) and inline
of it
- 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
- admin.py
- 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)
- models.py
- 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
- myapp/models.py
- Searchable (in a pop-up)
raw_id_fields = ('field_1',)
- Filter dropdown choices
formfield_for_foreignkeyclass 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)classMyModelAdmin(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]
object_id = request.resolver_match.kwargs["object_id"]
if object_id:
...
return super(MixerAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)from django.contrib.admin import widgets
class GenericRawIdWidget(widgets.ForeignKeyRawIdWidget):
# https://stackoverflow.com/questions/23617858/how-to-limit-choices-of-foreignkey-choices-for-django-raw-id-field/66429352#answer-36294798
url_params = []
def __init__(self, rel, admin_site, attrs=None, using=None, url_params=[]):
super(GenericRawIdWidget, self).__init__(
rel, admin_site, attrs=attrs, using=using
)
self.url_params = url_params
def url_parameters(self):
"""
activate one or more filters by default
"""
res = super(GenericRawIdWidget, self).url_parameters()
res.update(**self.url_params)
return res
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
# car not in raw_id_fields
kwargs["queryset"] = Car.objects.filter(owner=request.user)
# car in raw_id_fields
kwargs["widget"] = GenericRawIdWidget(
db_field.remote_field,
admin.site,
url_params={"owner__id__exact": request.user.id},
)
return super(MyModelAdmin, 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__"
- from
django.forms import ModelForm
- admin.py
from my_app.forms importMyModelAdminForm
class MyModelAdmin(admin.ModelAdmin):
form = MyModelAdminForm
- forms.py
- admin.py
- 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.py
- ...
- Using
generic relations as an inline
- Admin
widget for generic relations in django (code that has
some errors)
- django-generic-fk
- ContentTypeSelect (right version)
- django-generic-fk
- django-genericadmin
- django-genericrelationview
- django-admin-genericfk (does not work with Django 1.9)
- from model that has GenericForeignKey()
- Resizing forms
- Problemes / Problems
20I18n / l10n#
- Internationalization and localization
- Django packages for internationalization
- Rosetta
- Easymode
- Serialization
- Unicode in Python
- coding in file.py
# coding: utf-8
- in Python files:
from django.utils.translation import ugettext_lazy as_
...
verbose_name = _("Nom del model")from django.utils import translation
current_language = translation.get_language()from django.utils import translation
current_language = translation.get_language()
forced_language = 'ca'
translation.activate(forced_language)
...translation.activate(current_language)- Utilització amb variables / Usage with variables
- file1.py
# do not use aliases; string would not appear in django.po:
# from django.utils.translation import ugettext_lazy as _, ugettext_noop as _noop
from django.utils.translation import ugettext_lazy as _, ugettext_noop
my_var = ugettext_noop("My translatable string")
my_func(my_var)
- file2.py
from django.utils.translation import ugettext_lazy as _
def my_func(cadena)
print _(cadena)
- file1.py
- in templates:
- Internationalization: in template code
{% load i18n %}
{% trans "My text" %}
- Separació dels milers /
Thousand separation
- per exemple a l'admin
- no afecta a les respostes generades per Django Rest Framework
- IMPORTANT: cal adaptar les plantilles de les pàgines intermèdies de l'admin:
- ...template.html
{% load i18n l10n %}
... {{ obj.pk | unlocalize }}
- ...template.html
- settings.py
USE_THOUSAND_SEPARATOR = True
- 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)
...
- ...py
- 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',
...
- project_name/settings.py (order is important)
- Activation of i18n for
project_name:
- dependències / dependencies
- Mageia
urpmi gettext
- Mageia
- 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:
- settings.py
EXTRA_LANG_INFO = {
'oc': {
'bidi': False,
'code': u'oc',
'name': u'Occitan',
'name_local': u'occitan'
},
}
# Add custom languages not provided by Django
# https://stackoverflow.com/questions/38511925/add-custom-language-for-localization-in-django-app
LANG_INFO = dict(list(django.conf.locale.LANG_INFO.items()) + list(EXTRA_LANG_INFO.items()))
django.conf.locale.LANG_INFO = LANG_INFO
- settings.py
- si la llengua que voleu no està a la llista
LANG_INFO (django/conf/locale/__init__.py), caldrà
afegir-la als settings:
- project_name/settings.py
- Django > ...
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
- Django < ...
import os
PROJECT_PATH = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))LOCALE_PATHS = (
PROJECT_PATH+ '/locale/',
)
- Django > ...
- 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
- Project / New project
- lokalize
- workaround for non existing ignore option for
compilemessages
cd src/myproject/myproject../manage.pycompilemessages
django-admin.py compilemessages
- següents vegades / next times:
cd project_name
./manage.pymakemessages -a --ignore="env*"- more compatible with git merges:
- ./manage.py makemessages -a --ignore="env*" --no-location --no-wrap --no-obsolete
- more compatible with git merges:
django-admin.pymakemessages -a- translate locale/ca/LC_MESSAGES/django.po (e.g. using lokalize)
- workaround for non existing ignore option for
compilemessages
cdsrc/myproject/myproject
../manage.py compilemessages
- dependències / dependencies
- 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
- Django's I18N with third-party apps
- Django ignoring translated strings from third party module
- Handling i18n in Django projects
- Examples:
- Passos / Steps
- check that files in /path/to/third_party_app/*.py are using ugettext or ugettext_lazy
cd project_name- activate i18n for project
ln -s /path/to/third_party_app .django-admin.pymakemessages -a -sdjango-admin.py compilemessages[rm third_party_app]
- 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:
- la primera de settings.LANGUAGES
- settings.MODELTRANSLATION_DEFAULT_LANGUAGE
- 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}
)
- fora de les vistes:
- ...
- 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
- 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:
- get active language:
import modeltranslation
modeltranslation.utils.get_language()
- set active language (e.g. outside a view):
from django.utils import translation
translation.activate('ca')
- unset active language
translation.deactivate()
- Utilització amb djangorestframework
/ Usage with djangorestframework
- NOT NEEDED: How
to
handle translations when serializing?
- TranslateSerializer
- curl
curl --user admin:admin http://127.0.0.1:8000/api/toto/?format=json --header 'Accept-Language: ca'
- override language (force language by query params,
even if accept language is sent)
from django.utils import translation
class OverrideTranslateSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(OverrideTranslateSerializer, self).__init__(*args, **kwargs)
if kwargs.get('context', None):
request = kwargs['context'].get('request', None)
forced_lang = request.QUERY_PARAMS.get('lang',None)
if forced_lang:
translation.activate(forced_lang)curl --user admin:admin "http://127.0.0.1:8000/api/toto/1/?lang=fr" --header 'Accept-Language: ca'- will get fr version
curl -X PATCH -H "Content-Type:application/json" -d '{"name":"nouveau nom"}' --user admin:admin "http://127.0.0.1:8000/api/toto/1/?lang=fr" --header 'Accept-Language: ca'- will modify fr version
- NOT NEEDED: How
to
handle translations when serializing?
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')),
)
- settings.py
- 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)
- translation.py (parallel to models.py, where
translatable fields are defined)
- my_project
- 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')),
)
- settings.py
- 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
- models.py
- my_project
- Problemes / Problems
modeltranslation.translator.NotRegistered- Conflict between admin.autodiscover() and translator.register #233
- Solució / Solution
- in INSTALLED_APPS, 'modeltranslation' must
be at first place
- in INSTALLED_APPS, 'modeltranslation' must
be at first place
- Internationalization and localization of django models, with admin support (django-easymodel)
21Tests#
- 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
- 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
sudo su - postgres
- CentOS
su postgres
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
django.db.utils.OperationalError: server closed the connection unexpectedly
psycopg2.OperationalError: FATAL: the database system is in recovery mode
psycopg2.InterfaceError: connection already closed
django.db.utils.InterfaceError: connection already closed- Out of space running Django test suite on Postgres #1125
- Solució / Solution
- increase tmpfs size
psycopg2.InterfaceError: connection already closed- hi ha alguna màquina virtual executant una aplicació semblant?
- TransactionManagementError during testing with Django 1.10
- Solució / Solution
sudo systemctl restart postgresql.service- Persistent
connections
- ...
- 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
**{"HTTP_ACCEPT_LANGUAGE": "ca"})res =self.client.get(url, post_params_dict,
# cors headers in preflight (django-cors-headers)
**{"HTTP_ACCESS_CONTROL_REQUEST_METHOD": "GET"},res =self.client.options(url,
**{"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 resultprint("{} {}".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()
- Note that (implicit) PrimaryKeyRelatedField
has allow_null=True
- 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")
- using JSON:
- Solució / Solution
- when trying to set a related field (declared using
- 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_list = request.query_params["myparam"].split(",")
"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_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")Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- views.py
myparam_list = request.query_params.getlist("myparam")
#request.query_params["myparam"] returns only the last value: "second"
- test.py
myparam_list = ["first", "second"]
# <url>?myparam=first,second
res = self.client.get(url, {"myparam": myparam_list})
-
- empty file
- when trying to upload an empty file to a field
declared with allow_empty_file=True:
my_attached_file = serializers.FileField(write_only=True, required=False, allow_empty_file=True)
- Solució / Solution:
res = ...
- when trying to upload an empty file to a field
declared with allow_empty_file=True:
- Exemples / Examples
- 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})
- database
- Dades inicials en tests
unitaris / Initial dataset in unit tests
- Fixtures
- Loading fixtures in django unit tests
- Exemple / Example
from django.test import TestCase
class MyTestCase(TestCase):
fixtures = ['myfixture',]
...
- TestCase.setUpTestData()
- ...
- Fixtures
- 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')
- tests.py
./manage test [--keepdb] ...
- disable logging
- 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 Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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)
@patch("path_to.first_level")
def test_something(..., mock_first_level)
# levels with () must be patched with return_value
# <MagicMock name='first_level.second_level().third_level.forth_level().attribute' id='xxxxxx'>
mock_first_level.second_level.return_value.third_level.forth_level.return_value = Mock(attribute="...")
self.assertEqual(mock_first_level.second_level.call_count, expected_number_of_calls)@patch("path_to.first_level")
def test_something(..., mock_first_level)
# levels with () must be patched with return_value
# <MagicMock name='first_level.second_level().third_level.forth_level().attribute' id='xxxxxx'>
mock_first_level.second_level.return_value.third_level.forth_level.side_effect = [
Mock(attribute="..."),Mock(attribute="..."),
]
(my_def_dir/my_def_file.py)
def my_function(...):
return my_value(my_call_dir/my_call_file.py)
def my_caller_function(...):
returned_value = my_function()from mock import patch
@patch("my_call_dir.my_call_file.my_function")
def test_my_caller_function(..., mock_my_function):
mock_my_function.return_value = my_fake_valuefrom 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_objectdef 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}))
]
(my_dir/my_file.py)
import requests
class MyClass(object):
def my_caller_function(self, ...):
with requests.Session() as session:
res = session.post(...)from mock import patch, Mock
@patch("path.to.my_caller_function.requests")
def test_my_caller_function(self, mock_requests):
# when response.text is used
# <MagicMock name='requests.Session().__enter__().post().text' id='xxxxxx'>
mock_requests.Session.return_value.__enter__.return_value.post.return_value = Mock(status_code=200, ok=True, text=json.dumps({"key": "1234"}))
# when response.json() is called:
mock_requests.Session.return_value.__enter__.return_value.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):
# when response.text is used
mock_requests.Session.return_value.__enter__.return_value.post.side_effect= [
Mock(status_code=200, ok=True, text=json.dumps({"key": "1234"})),
Mock(status_code=200, ok=True, text=json.dumps({"key": "1234"})),
]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
def my_function():
client = boto3.client(...)
response = client.create_stack(...)
response = client.other_method(...)
from mock import MagicMock, patch
response_create_stack = {...}
@patch("boto3.client")
def test_my_function(self, mock_boto3_client):
mock_boto3_client_return_value = MagicMock()
# client.create_stack()
response_create_stack = [...]mock_boto3_client_return_value.create_stack.return_value = response_create_stack
# client.other_method()
response_other_method = [...]mock_boto3_client_return_value.other_method.return_value = response_other_method
mock_boto3_client.return_value = mock_boto3_client_return_value
...
@patch("boto3.client")
def test_my_function(self, mock_boto3_client):
# more compact version, without intermediate MagicMock explicit creation:mock_boto3_client.return_value.create_stack.return_value = response_create_stackmock_boto3_client.return_value.other_method.return_value = response_other_methodfrom mock import MagicMock, patch
response_create_stack = {...}
response_describe_stacks = {...}
@patch("boto3.client")
def test_my_function(self, mock_boto3_client):
# first time client.create_stack()
mock_create = MagicMock()
mock_create.create_stack.return_value = response_create_stack
# second time client.create_stack()
mock_update = MagicMock()
mock_update.describe_stacks.return_value = response_describe_stacks
mock_boto3_client.side_effect = [mock_create, mock_update]
...Boto3
def my_function():
s3 = boto3.resource("s3")
bucket = s3.Bucket(bucket_name)
deletion_response = bucket.objects.filter(
Prefix=...
).delete()from mock import patch
@patch("boto3.resource")
def test_delete_from_storage(self, mock_boto3_resource):
resource_object_filter_delete_return_value = [
{
"ResponseMetadata": {
"RequestId": "ABCDEF0123456789",
"HostId": "abcdefghij012345678ABC/AAAAA/aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvww=",
"HTTPStatusCode": 200,
"HTTPHeaders": {
"x-amz-id-2": "abcdefghij012345678ABC/AAAAA/aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvww=",
"x-amz-request-id": "ABCDEF0123456789",
"date": "Wed, 28 May 2085 07:36:01 GMT",
"connection": "close",
"content-type": "application/xml",
"transfer-encoding": "chunked",
"server": "AmazonS3",
},
"RetryAttempts": 0,
},
"Deleted": [{"Key": "path/to/my_key"}],
}
]
# NOTE: no "return_value" must be present after "objects", because if we inspect "delete" when it is called,
# with Eclipse by putting the cursor over it, there are no "()" after "objects":
# MagicMock: <MagicMock name='resource().Bucket().objects.filter().delete' id='139769384252368'>
mock_boto3_resource.return_value.Bucket.return_value.objects.filter.return_value.delete.return_value = (
resource_object_filter_delete_return_value
)
ec2_metadata ec2_metadata lib (my_dir/my_file.py)
from ec2_metadata import ec2_metadata
instance_id = ec2_metadata.instance_idfrom mock import PropertyMock, patch
@patch("my_dir.my_file.ec2_metadata")
def test_my_function(self, mock_ec2_metadata):
# mock ec2_metadata
type(mock_ec2_metadata).instance_id = PropertyMock(
return_value="i-xxxxxxxx"
)
type(mock_ec2_metadata).region = PropertyMock(return_value="eu-west-1")
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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")- ...
- should be:
- "django.db.utils.InterfaceError: connection already
closed"
- Solution:
- restart database service?
- Solution:
- ...
- ...
22REST#
- REST client
- Testing (djangorestframework)
- Communicating with RESTful APIs in Python
- Slumber
- python-requests
- 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)
- views.py
- 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
- OAuth
- XML
- YAML
- JSONP
- 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
- request.QUERY_PARAMS
- request.query_params
- query_params validation
- 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:
- clean()
- full_clean()
Differences between ModelSerializer validation and ModelForm
serializer:
- validate()
- validate()
Removal of validate() (field classes)
- to_internal_value
- pre_save
- post_save
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:
- (implicit)
Writable nested serialization:
- depth => read_only
- def create()
- def update()
print(serializer)
- write_only_fields
- extra_kwargs
= {
'is_admin': {'write_only': True}
}
HyperlinkedModelSerializer:
- view_name
- lookup_field
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')
- ListSerializer
- ListField
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.
- required
- default
- allow_null
- allow_blank
ListField
- child
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()
- (removed)
Relational fields
- queryset=...
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=...)
- default_validators=[UniqueTogetherValidator(queryset=..., fields=('field1','field2'))]
Generic Views
serializers.NullBooleanField serializers.BooleanField with allow_null=True [#7122] Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Library: parsers, renderers, ...
- Tutorial
- API Guide
- Serializers
- Views
- Routers
- Filtering
- 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
- virtualenv +
pip (e.g. for deployment with Apache)
- Configuració
/ Setting
- settings.py
INSTALLED_APPS = ( ... 'rest_framework', )REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
'PAGINATE_BY': 10,
...
}
- data
/ date
- settings.py
REST_FRAMEWORK = {
...
# https://docs.python.org/2/library/datetime.html#strftime-strptime-behavior
'DATETIME_INPUT_FORMATS': ['iso-8601','%Y-%m-%d %H:%M:%S'],
#'DATETIME_FORMAT': 'iso-8601',
# same as iso-8601, but force microseconds rendering when equal to 0
'DATETIME_FORMAT': '%Y-%m-%dT%H:%M:%S.%fZ',
'TIME_INPUT_FORMATS': ['iso-8601','PT%HH%MM%SS'],
'TIME_FORMAT':'PT%HH%MM%SS',
}- Django REST Framework-Specific Standards: DateTimes
- 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
- ...
- settings.py
- settings.py
- 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
- bulk creation, bulk update
- websockets
- django-rest-live (djangorestframework)
- "django-rest-live gets rid of the need for periodic polling GET requests to for resource updates after page load."
- based on: Django REST Framework & Channels
- django-rest-live (djangorestframework)
- GeoDjango
- Paginació / Pagination
- Documenting
your
API
- Swagger
- Specification
- conversion to pdf
- Export API Documentation as a PDF
- swagger-spec-to-pdf
- Instal·lació
npm install -g argparse electron-prebuilt yaml-js
npm install -g swagger-spec-to-pdf
- Ús
swagger2pdf -j -s my_api_docs.jsonswagger2pdf -y -s my_api_docs.yaml
- Instal·lació
- 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',
),
- urls.py
- Disable
- drf-spectacular
- Swagger
- supports i18n
- newest version for Django 1.x:
pip install "drf-spectacular==0.8.5" - Migració / Migration
- 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
- POST
- PUT
- PATCH
default is generated from - url pattern parameters
- queryset
- lookup_field
- filter_backends
- paginator
serializer_class
serializer_class
overwritten by
@swagger_auto_schemamanual_parameters- IN_PATH
query_serializer(all from a serializer)manual_parameters(one by one)- IN_QUERY
request_body- MySerializer
no_body
responsesrequest_body
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Swagger
- Problemes / Problems
- /docs/: 401 Unauthorized
- Solucions
- in settings, add BasicAuthentication:
- settings.py
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.BasicAuthentication", # needed by drf_yasg authenticated docs
"rest_framework.authentication.TokenAuthentication",
)
}- urls.py
schema_view = get_schema_view(
openapi.Info(
...
),
public=False,
# NOTE: IsAuthenticated requires "rest_framework.authentication.BasicAuthentication"
# in settings.REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"]
permission_classes=(permissions.IsAuthenticated,),
)- disable IsAuthenticated in urls.py (not recommended)
schema_view = get_schema_view(
openapi.Info(...
),
public=False,
# permission_classes=(permissions.IsAuthenticated,),
)
- 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
@swagger_auto_schema(
request_body = MyInputSerializer(),
responses = {
200: MyOutputSerializer()
}
)- Specify different serializer classes for request and response #336
- 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):
- Solució / Solution
- "AssertionError: Serializer class or
instance required, not unicode"
- 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().datafrom 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
- The
@swagger_auto_schema decorator
-
- Django
REST Swagger (deprecated)
- Swagger
- Documentation (0.3)
- Instal·lació / Installation
pip installdjango-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')),
...
]
- my_project/urls.py
- Static documentation
- Printing
Restler
API documentation
- SwaggerToMarkdown
- gem install titleize
- ./swagger-to-markdown.rb -r resources.json -p parameters.json -n Demo -o test.md -s pet.json
- pandoc
(a universal document converter)
- ...
- SwaggerToMarkdown
- Swagger data to PDF documentation
- Printing
Restler
API 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',
),
- Solució / Solution
- 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
- views.py
- Solution: when using get_serializer_class,
take into account that swagger is accessing
as HttpRequest instead of usual
rest_framework.request.Request
- serializers.BaseSerializer not working
- Swagger not working with Django BaseSerializer object
- Solució / Solution
- Use serializers.Serializer
- serializers.ListField(
child=serializers.IntegerField()
- Unable to introspect fields.ListField #367
- Solució / Solution
- Use respondcreate/django-rest-swagger
fork
- pip install
-e git+git://github.com/respondcreate/django-rest-swagger.git#egg=django-rest-swagger
- pip install
- Use respondcreate/django-rest-swagger
fork
- 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"))
- views.py
- Filter query parameters
- Should
query
params from filter appear in methods other
than GET (list)? #377 (cescp)
- Solució / Solution
- Should
query
params from filter appear in methods other
than GET (list)? #377 (cescp)
- YAML
Docstring
- 4.3.3 Data type fields
- How to show query parameter options in Django REST Framework - Swagger
- Syntax
-
yaml
class MyModelFilterSet(django_filters.FilterSet):
gui
top level
first level
key
value
description
field_1 = django_filters.XXFilter(...)
Response class
Parameters
Error Status Codes
with ViewSet with generics.xxxAPIView
create:
list:
retrieve:
update:
partial_update:
destroy:
GET: (bug)
POST:
PATCH:
PUT:
serializer:
my_app.serializers.MyModelSerializer
x
request_serializer:
...
?
response_serializer:
...
x
type:
field1:field2:
required:true
when not using serializer, fields can be directly described here
x
false
type:
string
integer
boolean
url
...
parameters:(Parameter Object)
(Data type fields)
- name:
my_name
Parameter
description:my_description"my desription with: colons"
label=
Description
paramType:
query
get parameters: ?foo=...&bar=...
Parameter Type
formpost parameters
bodyall parameters (in json?)
filefile 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: arraycollectionFormat: csv
items:
type: string
type: choice
enum:
- value_1
- value_2
- ...
parameters_strategy
form:query:
mergereplace
omit_parameters:
- query
- path
...
responseMessages:
- code:
403
...
HTTP Status Code message:My message
Reason
widget
Value
Data Type
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- 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
"""
- http://.../?foo=...&bar=...
- 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
- views.py
- Headers (*)
parameters:
- name: Authorization
paramType: header
required: true
- Override serializer by a simple description
- views.py
- ...
- views.py
- Choices
- Using the string label instead of integer value
- views.py
class ...
"""
---
parameters:
- name: my_choice_field_1
description: "My description"
paramType: form
type: choice
enum:
- my_value_option_1
- my_value_option_2
"""
- List, array
- Swagger UI doesn't seem to support Arrays yet? #982
- With yuml (not working)
- views.py
class MyModelViewSet(viewsets.ModelViewSet):
"""
My help
---
create:
parameters:
- name: prices
description: "Array of prices"
type: array
items:
type: string
collectionFormat: csv
- views.py
- 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()
- views.py
- 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()
- views.py
- Option 1 (recommended one) Serializer
specification is also needed in yaml
description
- File upload
- (?) Not working! Even if paramType
is specified as form, in the GUI it shows as
body, and any request gives a "403
undefined" error:
- "detail": "CSRF Failed: CSRF token missing or incorrect."
- CSRF token missing or incorrect when File upload path not exist.
- Solució / Solution
- verify that the dir where files are uploaded exists
- 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
- (?) Not working! Even if paramType
is specified as form, in the GUI it shows as
body, and any request gives a "403
undefined" error:
- Nested writable serializers
- Support for sending json object in raw post data #50
- Nested serializers: when combining form parameters and body, only body is sent. #360
- models.py
- ...
- serializers.py
- ...
- views.py (Problem: when body is specified,
"Try it out!" does not send other
parameters)
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows a user to be viewed or edited.
---
create:
parameters:
- name: profile
description: profile of the user
paramType: body
pytype: account_manager.serializers.ProfileSerializer
"""
- Swagger
- 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})
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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 Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- 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":...}
- header:
- access
- header:
{"typ":"JWT", "alg":"HS256"} - payload:
{"token_type":"access", "exp":..., "iat":..., "jti":..., "user_id":...}
- header:
- refresh
- 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
- signing algorithm
- django-rest-framework-jwt
- fork from jpadilla/django-rest-framework-jwt, unmantained
- 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),
}
- my_project/my_project/settings.py
- 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'),
)
- my_project/my_project/settings.py
- 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
}
- my_project/settings.py
- 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"})
- import slumber
- using slumber (NOTE: does not work
with '-' in urls)
- ...
- ...
- djangorestframework-simplejwt
-
- 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,)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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',),
}
- settings.py
- my_app
- views.py
class PlaceViewSet(viewsets.ModelViewSet):
filter_fields = ('type',)
- views.py
- my_project
- 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=...
- views.py
- 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=...
- views.py (>=1.0)
- 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#myfield = CharInFilter(field_name='myfield', lookup_expr='in')lookup_expr='in' is the default for BaseInFilter
class Meta(object):
model = MyModel
- example
- 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 = NonValidatingMultipleChoiceFieldclass 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)
- ...
- BaseInFilter
- 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
- list of objects that begin within the
next 10 minutes and 20 seconds:
- Boolean
(lower case)
- my_app/models.py
class MyModel(models.Model):
is_ok = models.BooleanField(default=True)
- my_app/views.py
- coerce: cast to a type
from distutils.util import strtobool
import django_filtersfrom 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
- my_app/models.py
- MethodFilter
with boolean
- IMPORTANT: MethodFilter and Filter.action replaced by Filter.method
- my_app/models.py
class MyModel(models.Model):
begin = models.DateTimeField(_("Begin"), help_text=_("Begin date"))
end = models.DateTimeField(_("End"), help_text=_("End date"))
- my_app/filters.py
import django_filters
class BooleanMethodFilter(django_filters.MethodFilter):
field_class = forms.NullBooleanField
- my_app/views.py
from my_app.filters importBooleanMethodFilter
BOOLEAN_CHOICES = (('false', 'False'), ('true', 'True'),)
class MyModelFilterSet(django_filters.FilterSet):
is_active = BooleanMethodFilter(action='get_active', choices=BOOLEAN_CHOICES, help_text=_("Filter by objects that are active"))
def get_active(self, queryset, value):
# get now in UTC
now = timezone.now()
qs = queryset
if value != '':
value_bool = strtobool(value)
if value_bool:
qs = queryset.filter( begin__lte=now, end__gte=now )
else:
qs = queryset.filter( Q(begin__gte=now) | Q(end__lte=now) )
return qs
class Meta:
model = MyModel
fields = ['is_active',]
- Choices
- models.py
class TotoModel(models.Model):
FIELD_A_CHOICES = (
(0,'first'),
...
)
field_a = models.IntegerField(choices=FIELD_A_CHOICES)
- views.py
import django_filters
from my_app.models import TotoModel
class TotoFilter(django_filters.FilterSet):
field_a = django_filters.ChoiceFilter(choices=TotoModel.FIELD_A_CHOICES, help_text="Filter by field_a: %s" % ', '.join([v for k, v inTotoModel.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=firsthttp://.../totos?field_a=0
- models.py
- django-rest-framework-filters
- django-filter
(used by DjangoFilterBackend)
-
- 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)
- models.py
- 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'
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- request (
- 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:
- ...
- >>> from rest_framework import renderers
- 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')})
- python-requests
- create a cover
- settings.py
- 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 statusfrom 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 applicationfrom rest_framework importserializers
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)
raiseValidationError("Error when creating.")
return application- and exception has to be captured at perform_create in views.py ViewSet
- views.py
- 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 = PlaceSerializerclass 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)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
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)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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)
- def get_queryset(self):
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 pre_save(self,
obj):
- [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()
- # drf 2.x
{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 get_object(self):
- [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
- def pre_save(self,
obj):
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(...)Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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,
+ Routersrouter = 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
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- from rest_framework import viewsets
- router automatically creates, from a ViewSet:
- urls
- url names
- api_root automatically generated by DefaultRouter (but not by SimpleRouter)
- registre / registration
(urls.py):
from django.conf.urls import patterns
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register(r'prefix', viewset, base_name='basename')
urlpatterns = patterns('',
url(r'^', include(router.urls)),
)
- alternative to router (use *APIView instead of
ViewSet):
urlpatterns = patterns('',url(r'^prefix/$', views.ExtendedUserUpdateAPIView.as_view(), name='basename-detail' ),
)
- Routers aniuats /
Nested routers
- drf-nested-routers
- Installation
pip install drf-nested-routers
- Usage:
- views.py
class NameserverViewSet(viewsets.ViewSet):
def get_queryset(self):
domain_pk = self.kwargs.get('domain_pk', None)
if domain_pk:
nameservers = Nameserver.objects.filter(domain=domain_pk)
else:
nameervers = Nameserver.objects.all()
return nameservers
- views.py
-
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)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Installation
- drf-nested-routers
- ...
-
- Views
- Parsers
- Tutorial 1: Serialization
- Exemples / Examples:
- XMLParser
pip install defusedxml
- 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
- my_project/settings.py
- 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)
- XMLParser
- Serialització /
Serialization
- Serializers
depth- Dealing
with
multiple objects
- Bulk
update
- Deserializing
multiple
objects for update
get_identity
- Bug
when
using bulk create (
'RelationsList' object has no attribute...) - External libraries for bulk update
- Deserializing
multiple
objects for update
- Bulk
update
- 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)
- models.py
- Default value
- Boolean
- 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',)
- serializers.py
- 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):
...
- serializers.py
- Serialització
dels queryparams / Queryparams serialization
- Use a serializer for the query parameters
- Good Practice to Handle Incoming Data in DRF
- Exemples / Examples
- ...
classMyQueryparamsSerializer(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.get("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):
...
- views.py
- Python rest framwork: different serializers for input and output of service?
read_only=Truewrite_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 importMyResponseSerializer
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())
- solves problem:
- Paraules
reservades com a camps / Reserved words as fields
- Remapping data keys to serializer attributes #967
- How to declare a form field in Django, if it has the same name as a Python keyword?
- Solució / Solution
- serializers.py
class MyModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(MyModelSerializer, self).__init__(*args, **kwargs)
# as "class" is a reserved word in Python, conversion must be done now, and not before
self.fields['class'] = self.fields['my_class']
del self.fields['my_class']
class Meta:
model = MyModel
fields = ('my_class',)
- serializers.py
- 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',)
- serializers.py
- 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()) )
classMyModelSerializer(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: truetype: choiceenum:
- zero
- one
- two
parameters:
- name: my_field
description: "Some description here"
required: true
paramType: form
type: choice
enum:
- zero
- one
- two
"""
...
- serializers.py
- 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
- obj.save
- ModelClass._default_manager.create(**validated_data)
- TaggitSerializer._save_tags
- _TaggableManager.set
- self.remove
- self.add
- _TaggableManager.set
- ModelSerializer.create
- super
- TaggitSerializer.create
- super
- MyModelSerializer.create
- self.create
- BaseSerializer.save
- serializer.save
- MyModelViewSet.perform_create
- self.perform_create
-
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)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- CreateModelMixin.create
- Serializers
relations
- ManyToMany with through
- GenericForeignKey
- ...
-
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)
- write: better try
SlugRelatedFieldinstead
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.dataoutput 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
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 2.x
- 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)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
-
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',)
...
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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
- and required:
- field is not writable:
- models.py
- Problemes / Problems:
- AttributeError at ...
'QuerySet' object has no attribute '...'- Solució / Solution
- Comproveu que la crida al serialitzador la
feu amb / Check that the call to the
serializer contains:
many=True
- Comproveu que la crida al serialitzador la
feu amb / Check that the call to the
serializer contains:
- Solució / Solution
- AttributeError at ...
- 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)
- Hexadecimal integers
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',)
- models.py
- 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
}
- serializers.py
- 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()
- serializers.py
- 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
}
- serializers.py
- 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',)- ...
- models.py
- Allow
null:
- To avoid error:
"This field may not be null"
serializers.SlugRelatedField(... allow_null=True )- Test djangorestframework using None
- To avoid error:
- ...
- WritableField
- Serializers
- 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_queryset(self)
- get_object(self)
- get_serializer_class(self)
- get_paginate_by(self)
- pre_save(self, obj)
- post_save(self, obj, created=False)
- perfom_create(self, serializer)
- perfom_update(self, serializer)
- get_serializer
- ...
+
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
actions provided by mixin
(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
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
+
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)
- queryset
- serializer_class
- lookup_field
- lookup_url_kwarg
- filter_backends
- pagination_class
- get_queryset()
- get_object()
- get_serializer()
- get_serializer_class()
- get_serializer_context()
- filter_queryset()
- paginator()
- paginate_queryset()
- get_paginated_response()
- pre_save(self, obj)
- post_save(self, obj, created=False)
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
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- APIView
- See general MVC
table
- 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)
- See general MVC
table
- GenericAPIView
- See general MVC table
- 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)classOtherModelSerializer(serializers.ModelSerializer):
...
class MixedOutputSerializer(serializers.Serializer):
first_number = serializers.IntegerField()
results =OtherModelSerializer(many=True)
class TotoAPIView(generics.GenericAPIView):
# output serializer (just for swagger, because recursive serialization does not work with mixed ORM and non-ORM)
serializer_class = MixedOutputSerializer
# define get method
# this could also be achieved by using generics.ListAPIView (no need to define get, then)
def get(self, request, *args, **kwargs):
other_queryset = OtherModel.objects.all()
other_serializer = OtherModelSerializer(queryset, many=True)
return Response({'first_number':MyModel.objects.count(),
'results':other_serializer.data})
- Example with
django-solo
- my_project/settings.py
INSTALLED_APPS = (
...
'solo',
)
- my_app/models.py
from solo.models import SingletonModel
class PricingLimits(SingletonModel):
...
- my_app/serializers.py
class MySoloModelSerializer(serializers.ModelSerializer):
class Meta:
model = MySoloModel
- my_app/views.py
class MySoloModelView(generics.GenericAPIView):
serializer_class = MySoloModelSerializer
def get(self, request):
instance = MySoloModel.get_solo()
serializer = self.get_serializer(instance)
return Response(serializer.data)
- my_app/urls.py
url(r'^my_view/$', views.MySoloModelView.as_view(), name='mysolomodel-detail' ),
- my_project/settings.py
- ViewSets
- See general MVC table
- 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.
- "Django REST framework allows you to combine the
logic for a set of related views in a single class,
called a ViewSet. In other frameworks you may also
find conceptually similar implementations named
something like 'Resources' or 'Controllers'.
A ViewSet class is simply a type of class-based View, that does not provide any method handlers such as .get() or .post(), and instead provides actions such as .list() and .create().
The method handlers for a ViewSet are only bound to the corresponding actions at the point of finalizing the view, using the .as_view() method.
Typically, rather than explicitly registering the views in a viewset in the urlconf, you'll register the viewset with a router class, that automatically determines the urlconf for you." - 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):
passclass CreateUpdateViewSet(mixins.CreateModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
passclass CreateRetrieveUpdateViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
passclass UpdateViewSet(mixins.UpdateModelMixin,
viewsets.GenericViewSet):
passclass ListUpdateViewSet(mixins.ListModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
passclass 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
-
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- Marking
extra
methods for routing (@action)
- action
decorator replaces list_route and detail_route
(DRF 3.9)
@detail_route() -> @action(detail=True)- @list_route() -> @action(detail=False)
@action(...)- detail
detail=Truedetail=False
- methods
methods=["get"]
methods=["post"]
permission_classes=(permissions.AllowAny,)serializer_class=MyOutputSerializer- ...
- detail
- 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(methods=['post'])detail=True,
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/$
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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):
- check that your function has
kwargs parameter:
- @detail_route not working with
lookup_field
- deprecated
- GET:
@link()
- POST:
@action()
def set_password(self, request, pk=None)@action(permission_classes=[IsAdminOrIsSelf])@action(methods=['POST', 'DELETE'])
- GET:
- action
decorator replaces list_route and detail_route
(DRF 3.9)
-
- Renderers
- Format suffixes (to choose the render based on request suffix)
- HTML
- HTML & Forms
- StaticHTMLRenderer
- TemplateHTMLRenderer
-
views.py urls.py templates using Django (no DRF) from django.http import Http404
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
@login_required(login_url="/accounts/login/")
def list(request):
item_list = MyModel.objects.order_by("-begin")[:5]
context = {
"user": request.user,
"item_list": item_list,
}
return render(request, "tata/mymodel_list.html", context)
@login_required(login_url="/accounts/login/")
def detail(request, uuid):
try:
item = MyModel.objects.get(uuid=uuid)
except MyModel.DoesNotExist:
raise Http404("Item does not exist")
context = {"item": item}
return render(request, "tata/mymodel_detail.html", context)from django.urls import path
from . import views
urlpatterns = [
# ex: /lives/
path("toto/", views.list, name="mymodel_list"),
# ex: /lives/xx-xxx/
path("toto/<uuid>/", views.detail, name="mymodel_detail"),
]
templates/tata/mymodel_list.html
<h1>MyModel</h1>
Logged as user: {{ user }}
{% if results %}
<ul>
{% for item in item_list %}
<li><a href="/toto/{{ item.uuid }}/">{{ item.name }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No items are available.</p>
{% endif %}
<h1>MyModel</h1>
Logged as user: {{ user }}
<p>
UUID: {{ item.uuid }}
</p>
using DRF APIView ...
using DRF GenericAPIView ...
using DRF ViewSet from rest_framework import viewsets, permissions
from rest_framework.renderers import TemplateHTMLRenderer
from .models import MyModel
from .serializers import MyModelSerializer
class MyModelViewSet(viewsets.ReadOnlyModelViewSet):
# APIView
renderer_classes = [TemplateHTMLRenderer]
permission_classes = [permissions.IsAuthenticated]
# GenericAPIView
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
lookup_field = "uuid"
def list(self, request, *args, **kwargs):
# TemplateHTMLRenderer
self.template_name = "tata/mymodel_list.html"
return viewsets.ReadOnlyModelViewSet.list(self, request, *args, **kwargs)
def retrieve(self, request, *args, **kwargs):
# TemplateHTMLRenderer
self.template_name = "tata/mymodel_detail.html"
return viewsets.ReadOnlyModelViewSet.retrieve(self, request, *args, **kwargs)from django.urls import path
from rest_framework.routers import DefaultRouter
mymodel_router = DefaultRouter()
mymodel_router.register(r"mymodelprefix", MyModelViewSet, basename="mybasename")
urlpatterns = [
...
path("toto/", include(mymodel_router.urls)),
]templates/tata/mymodel_list.html
<h1>MyModel</h1>
Logged as user: {{ user }}
{% if results %}
<ul>
{% for item in results %}
<li><a href="/toto/mymodelprefix/{{ item.uuid }}/">{{ item.name }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No items are available.</p>
{% endif %}
<h1>MyModel</h1>
Logged as user: {{ user }}
<p>
UUID: {{ uuid }}
</p>
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Examples
- XMLRenderer
- django-rest-framework-xml
- XML
in Python
- Pretty printing
XML in python
- Newlines in text node
- Pretty printing
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)
- XMLRenderer
- 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 importUriListRenderer
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' ...
- renderers.py
- 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
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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)
- models.py
- 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'),
)
- models.py:
- Example: action on a queryset
- see also admin
- ...
- Example: action on a single object:
- see also highlight
- Example: toto1/{toto2} (instead of default toto1/{pk});
returns only entries owned by user that performs the query (
curl -u usuari:contrasenya)- models.py
class Toto(models.Model):
user = models.ForeignKey(User)
toto2 = models.ForeignKey(Toto2)
...
- views.py
class TotoViewSet(viewsets.ModelViewSet):
queryset = Toto.objects.all()
serializer_class = TotoSerializer
permission_classes = (permissions.IsAuthenticated,)
lookup_field = 'toto2'
# when creating an entry, automatically put the user on the object, according to the request
def pre_save(self, obj):
obj.user = self.request.user
# only entries that belong to user
def get_queryset(self):
user = self.request.user
return user.userchallenge_set.all()
- urls.py
router.register(r'userchallenges', views.UserChallengeViewSet)
- models.py
- 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)
- views.py
- 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):utilitatsPygmentsRoot.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 Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- [djcode/mysite/mysite/]settings.py
-
INSTALLED_APPS = (
...,
'djangorestframework'
)
-
- Example "Getting
started
- Views"
- cd mysite
- python manage.py startapp resourceexample
- settings.py
-
INSTALLED_APPS = (
...
'resourceexample',
)
-
- urls.py
...
# rest
from resourceexample.views import ExampleView, AnotherExampleView
urlpatterns = patterns('',
url(r'^$',ExampleView.as_view(), name='example-resource'),
url(r'^(?P<num>[0-9]+)/$', AnotherExampleView.as_view(), name='another-example'),
...
)
- cd resourceexample
- forms.py
from django import forms
class MyForm(forms.Form):
foo = forms.BooleanField(required=False)
bar = forms.IntegerField(help_text='Must be an integer.')
baz = forms.CharField(max_length=32, help_text='Free text. Max length 32 chars.')
- views.py
from django.core.urlresolvers import reverse
fromrest_framework.views import View
fromrest_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))
- forms.py
- Test:
- Other examples
...
- ...
- Tastypie
- Documentation
- Getting started (api register)
- Resources
- Validation (forms)
- Info:
# easy-install django-tastypiedjango-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 ModelResourcefrom tastypie.authentication import BasicAuthenticationfrom 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 importpatterns, include, url
from api import LlibreResource
llibre_resource = LlibreResource()
urlpatterns = patterns('',
(r'^api/', include(llibre_resource.urls)),
...
)
- models.py
- 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
- from command line:
- 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)),
)
- api.py
- 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"
- cd llibres
- Example (Resource) (Using Tastypie With Non-ORM Data Sources)
- Documentation
23Loggers#
- 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")
- my_project/settings.py
- 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_loggingpackage- manually:
cd myprojectmkdir local_loggingtouch local_logging/__init__.py
- Eclipse:
- (right-click over my_project) New -> PyDevPackage: local_logging
- manually:
- 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,
...
- Rotació i permisos
/ Rotation and permissions
- Simple Log to File example for django 1.3+
24Activitat i notificacions / Activity and notifications#
- Info
- Paquets / Packages
- Django packages:
-
package
info
Django Rest Framework eventlog django-activity-stream
django-notifications - based on django-activity-stream
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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.
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Instal·lació / Installation
pip install django-activity-stream- Opcional / Optional
- To add
custom data to an action:
pip install django-jsonfield
- To add
custom data to an action:
- 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__.pydefault_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 yetfrom django.contrib.auth.models import Userfrom 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
- Feature request: Store actions made by anonymous users #16
- Solució / Solution
- Feu servir un altre model (en lloc de User) com a actor (verb en passiva) / Use another model (instead of User) as the actor (verb in passive)
- django.core.exceptions.AppRegistryNotReady: Apps
aren't loaded yet.
- 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)unreaddeletedemailed
- 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'- PassThroughManager removed on django-model-utils==2.4
- Solució / Solution
pip install django-model-utils==2.3.1
- 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 actornotify.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.
notifications = Notification.filter(action_object__id=my_id)- Filtering on action_object: Field 'action_object' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. #281
- Solució / Solution
- Use:
action_object_object_id, target_object_id, actor_object_id- get notifications whose action object
is myinstance:
myinstance_ct = ContentType.objects.get_for_model(myinstance)
notifications = Notification.filter(action_object_content_type=myinstance_ct, action_object_object_id=myinstance.id)
- get notifications whose action object
is myinstance:
- GenericRelation
- Use:
- 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
- JSON
Adaptation
- 2.5.4: added jsonb support
- JSON
Adaptation
- 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
- 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.
- derivat de django-activity-stream,
amb aquests camps addicionals / derived from django-activity-stream, with these additional fields:
25Correu electrònic / Email#
- Sending
email
- ADMINS: to send emails (Error reporting) when a 500 error occurs in production (DEBUG=False)
-
error 500 when DEBUG=False
mail_admins()
send_mail()
others (allauth, ...)
from
SERVER_EMAILfrom_email=...DEFAULT_FROM_EMAILto
ADMINSrecipient_list=[...]...
backend
EMAIL_BACKENDSMTP backend host EMAIL_HOSTport EMAIL_PORTuser EMAIL_HOST_USERpassword EMAIL_HOST_PASSWORDfrom
DEFAULT_FROM_EMAILsecurity
EMAIL_USE_TLS
EMAIL_USE_SSL
client certs
EMAIL_SSL_CERTFILEEMAIL_SSL_KEYFILE
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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>'
- Install one email
server:
- Test
- Testing tools
- Email
service
django.core.mail.backends.locmem.EmailBackend
- Email
service
- 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')
- Testing tools
- SMTP services
- AWS SES
- boto3
- django-ses
- Verificació d'una adreça de correu electrònic / Email address verification
26Push 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 certificate from a pkcs12 file:
- 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
- one of the following:
- 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.ceropenssl 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
- Comproveu que el token que ha generat l'aplicació
correspon al mode de funcionament del servidor (*):
- FCM - Firebase Cloud Messaging (Google)
- GCM - Google Cloud Messaging (replaced by FCM)
- APNS (Apple)
- Libraries
- django-push-notifications
(Adys
fork)
- APNS, FCM/GCM, WNS
- DRF
- installation
- using pip
pip install django-push-notifications
- from source
pip install django-uuidfieldcd ~/src/git clone https://github.com/Adys/django-push-notifications.gitcd django-push-notificationssu; 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']",
}
- using pip
- 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)
- apns.py
- Solució / Solution
- El servidor retorna "Error: NotRegistered"
- GCM doesn't work on Android 4.2.2?
- No NotRegistered
message for an uninstalled registration_id
- one hour delay
- Solució / Solution
- (?) gcm.py
- delay_while_idle = False
- (?) gcm.py
- El missatge no es rep perquè té caràcters
especials / Message is not received because it
contains special characters:
- django-iphone-push
- feedback
- PyAPNs
- django-push-notifications
(Adys
fork)
27Slack#
- 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
- chat:write
- channels:read
- (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
- ...
- list of conversations
- ...
- Basic
app setup
- Paquets / Packages
- Ús
- ...
28Tasques / Tasks#
- Using channels (websockets)
- Celery
(asynchronous processing)
- First steps with Celery
- Índex / Index
- Using Celery
with Django
- General settings
- django-celery
- Run the worker process
- Monitoratge / Monitoring
- Ús /
Usage
- Puntual
/ Single
- Option 1: from views.py
- Option 2: from models.py
- Example 1: image conversion, all local
- Example 2: image conversion, AWS S3
- Puntual, a un temps especificat / Single, at a given time
- Periòdic / Periodic
- Puntual
/ Single
- Using Celery
with Django
- 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'
- myproject/myproject/celery.py
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
- How to properly configure djcelery results backend to database
- Tasks, Workers
- python manage.py celeryd -E
- python manage.py celerycam
- TaskMeta
# djcelery
from djcelery.models import TaskMeta
class TaskMetaAdmin(admin.ModelAdmin):
readonly_fields = ('result',)
admin.site.register(TaskMeta, TaskMetaAdmin)
- 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
- worker
- daemon
(for production)
- Running the worker as a daemon
- Daemonization
-
service
type
file
config file
usage
Init script: celeryd init.d /etc/init.d/celeryd
/etc/default/celeryd
/etc/init.d/celeryd {start|stop|restart|status}
Init script: celerybeat /etc/init.d/celerybeat
/etc/default/celerybeat
/etc/default/celeryd
/etc/init.d/celerybeat {start|stop|restart}
Service file: celery.service systemd /usr/lib/systemd/system/celery.service
(see celery.service for CentOS)
/etc/conf.d/celery
(see celery.conf for CentOS)
systemctl {start|stop|restart|status} celery.service
/usr/lib/systemd/system/celery.service
(see celerybeat.service for CentOS)systemctl {start|stop|restart|status} celerybeat.serviceAquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Example (including celerybeat)
- CentOS
- Examples
- celery / extra / systemd
- Systemd service file is invalid #2131
- Troubleshooting
- systemd issues
- celery issues
- celery multi is working strange with systemd #3459
- celery.service
and celery.conf don't work
#3165
C_FAKEFORK=1 systemctl start celery.service
- Systemd service file is invalid #2131
- 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/celerymkdir -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 -
- d /var/run/celery 0755
celery celery -
- included in
celery[beat].service:
- 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
- /var/{log,run}/celery/...
- 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 shellfrom 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
- check rabbitmq
queues for
virtual host
celery_vhost:
- should show console
output (in yellow) with
prints when calling task
functions (tasks.py)
from another console:
/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
- should show:
- start service
systemctl start celery.servicesystemctl status celery.service- should show:
Active: active (running)
tail -f/var/log/celery/w1.log- should show prints from called functions (tasks.py)
- if status is showing:
- Active: inactive (dead)
- try again to start
- should show:
journalctl -u celerymore /var/run/celery/*more /var/log/celery/*
- rabbitmq is running and permissions are ok (BROKER_URL)
- manually start and stop of
worker(s):
- 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')
- Examples
- 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.shsudo service celeryd start
sudo service celerybeat start
- celeryd.conf (/etc/default/celeryd)
- CentOS
- command
line (for development)
- Monitoratge /
Monitoring
- Monitoring and Management Guide
- Progrés / Progress
- Building Progress Bars for the Web with Django and Celery
- Django-Celery Progress Bar backend (djangosnippets)
- Applications
- celery-progress
- jobtastic
- tox (standardize testing in Python)
- 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 shellfrom my_app.tasks import *do_something()
- or, directly from a single command
- manage.py: ordres pròpies / custom commands
- Standalone Django scripts
DJANGO_SETTINGS_MODULE=myproject.settings env/bin/python -m my_app.tasks -c 'do_something()'
- myproject/my_app/tasks.py
- 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
- myproject/my_app/models.py
- 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 subprocessclass 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
- myproject/my_app/models.py
- Example 1: image
conversion, all local (consider django-stdimage)
- Option 1: from views.py
- 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
# ifCELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler',
# these entries will be included in the database,every time the celery beat is restartedCELERYBEAT_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/settings.py
- 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 infocelery -A myproject worker -B -l info
- In celery 3.1,
making django periodic task
- Puntual /
Single
- General
(project-wide) settings
29Cron#
- Celery periodic tasks
- django-cron
(Tivix) (github)
- Installation (one of the following):
pip install django_cronpip install -e git://github.com/Tivix/django-cron.git#egg=django_cron
- From Django 1.7:
python manage.py makemigrations django_cronpython 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 messagefrom 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 -
- install_crontab.sh
- una vegada / once:
- Installation (one of the following):
- django-cron (code.google)
30Serialització / Serialization#
31Importació i exportació / Import and export#
-
formats
xml
json
csv
xls
xlsx (native)
serialization
ie
ie
django-adaptors
i
i
django-data-importer
i
i
i
django-import-export tablib -
ie
ie
ie
djangorestframework
ie
ie
Panda
e
e
django-rest-framework-csv
ie
xlrd / xlwt
ie
openpyxl
ie Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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 importMyMainSerializer,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)
- this implementation is needed in order to keep fields
order as specified (in serializer or in data itself),
when using
- 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
- parsers.py
- 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")
- renderers.py
- 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
- parsers.py
- Ordered CSVRenderer
- django-data-importer
- django-import-export
(github)
- based on tablib
- botons a l'admin
- accions a l'admin
- csv, xls, ...
- django-adaptors
- Marmir
- Python Excel: xlwt / xlrd
- See also: openpyxl
- Outputting Excel with Django
- xlwt convenience methods
- Exporting data to Excel or PDF file in Django
- huDjango serializers
- save to:
- a local file:
...
wb.save('toto.xls')
- an http response:
...
# Django < 1.7:
#response = HttpResponse(mimetype="application/ms-excel")
# Django 1.7:
response = HttpResponse(content_type="application/ms-excel")
response['Content-Disposition'] = 'attachment; filename=%s' % fname
wb.save(response)
- a local file:
- 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')
32Gràfiques / Charts#
33SMS#
- Django Packages: SMS sending
- Sending an SMS to a Cellphone using Django
- Gateways
-
gateways
SMSGlobal Clickatell iTagg mssgbox
Twilio googlevoice
(custom) django-sendsms x
x
x
django-sms-gateway x
x
django-sms
django-smsgate
x
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- django-sendsms
pip install django-sendsms- settings.py
INSTALLED_APPS = (
...
'sendsms',
)# django-sendsms
SENDSMS_BACKEND = 'sendsms.backends.smsglobal.SmsBackend'
SMSGLOBAL_USERNAME = 'xxx'
SMSGLOBAL_PASSWORD = 'yyy'
34Storage#
35Etiquetes / Tags#
- Etiquetes / Tags
- django-taggit
pip install 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)Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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
- Update
TagListSerializerField to ListField subclass #832
- old version (<=3.0.0)
class TagListSerializerField(serializers.Field):tags = '["first tag","second_tag"]'
- new version (>=3.1.0)
class TagListSerializerField(serializers.ListField):tags = ["first tag","second_tag"]
- old version (<=3.0.0)
- Herència / Inheritance
- Update
TagListSerializerField to ListField subclass #832
- With djangorestframework
- django-taggit-serializer
- Installation
- from git (to avoid KeyError)
pip install -e git://github.com/glemmaPaul/django-taggit-serializer#egg=django-taggit-serializer
- from git (to avoid KeyError)
- Problemes / Problems
- KeyError: 'tags'
- Getting KeyError on partial update (PATCH) when 'tags' not included #7
- Solution
- install master git version
- KeyError: 'tags'
class TagListSerializerField(serializers.Field):
"""
Serializer for tags.
"""
# deserialization
def to_internal_value(self, data):
if type(data) is not list:
# try to force a list from a string: "['a','b']"
data = ast.literal_eval(data)
if type(data) is not list:
raise ParseError("expected a list of data")
return data
# serialization
def to_representation(self, obj):
if type(obj) is not list:
return [tag.name for tag in obj.all()]
return obj
- Installation
- Using django-rest-framework with tagged items (django-taggit)
- Using Django-taggit with django-rest-framework, i'm not able to save my tags
- django-taggit-serializer
- django-taggit
- Jerarquies / Hierarchy
- Django: Hierarchical tags with taggit and treebeard
- django-treebeard
- django-mptt (Modified Preorder Tree Traversal)
- django-categories
- django-tagging
- django-tagging-ng
- A "category" Data Model
- Etiquetes jeràrquiques / Hierarchical tags
36Metadades / Metadata#
- Paquets / Packages
- thoas / django-metadata
- Redis
- rafaelsierra / django-metadata
- mredar / django-dublincore
- thoas / django-metadata
37Push#
- 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+
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
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"],
})
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
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
)
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- 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
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- Tutorial
Part 1: Basic setup
- Creating a project
- create a general directory, install
Python3 with virtualenv and install Django
mkdir -p ~/src/mysite_wscd ~/src/mysite_wsvirtualenv-3.5 env3source env3/bin/activatepip install --upgrade pippip install djangopip install -U channels
- create a django project: mysite_ws
django-admin startproject mysite_ws .
- create a general directory, install
Python3 with virtualenv and install Django
- 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"
- Creating a project
- 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.servicedocker 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'}
- Add the room view
- Tutorial
Part 3: Rewrite Chat Server as Asynchronous
- Rewrite the consumer to be asynchronous
- chat/consumers.py
- ...
- 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
pip install selenium
- chat/tests.py
- Install
- Testing the views
- ...
- Resum / Summary
- 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
- Daphne
- How To Deploy Django Channels To Production (uwsgi + Daphne, Nginx)
- Daphne sevice unit for systemd is not working, daphne.socket vs daphne.sevice #202
- Get number of active connections (e.g. number
of simultaneous users connected to chat)
netstat -pna --ip | grep :443 | wc
- Problemes /
Problems
-
client error logs
info solution
/var/log/nginx/error.log /var/log/messages
?
[alert] 2398#0: 1024 worker_connections are not enough
Optimal value for Nginx worker_connections - nginx.conf
- events {
#worker_connections 1024;
worker_connections 8192;
}
- events {
[info] peer closed connection in SSL handshake while SSL handshaking
[info] client closed connection while waiting for request
[error] recv() failed (104: Connection reset by peer) while proxying upgraded connection
Nginx error message when closing websocket connections #1021
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
- nginx.conf
-
- 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- Solution?
- /var/log/messages
- Daphne
- ...
- Packages related to WebSockets
- django-websocket-redis (Redis, uWSGI, Nginx)
- django-omnibus (Tornado, ZMQ)
- django-socketio
- django-websocket
- django-rest-live
(djangorestframework)
38Amazon 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
- instal·leu / install boto
pip install django-storages
- instal·leu / install boto
- 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 settingsMediaRootS3BotoStorage = 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_LOCATIONclass 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_NAMEclass 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_domainself.storage = MyConfigurableS3Boto3Storage(bucket_name=bucket_name, custom_domain=custom_domain)
else:# use storage with default valuesself.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
Aquesta taula continua cap a la dreta — desplaça-la o gira el mòbil.
-
- 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
- How
to Dynamically Select Storage in Django FileField
- Views
- Problemes / Problems
[Errno 104] Connection reset by peer- uploading file to s3 results in error 104 (connection reset) #2207
- Solució / Solution:
- use S3Boto3Storage instead of S3BotoStorage
- consider upgrading django-storages (1.7.1 is working fine)
- 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/ maybex-amz-security-tokenis 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
- pot ser que encara quedi
- Solució / Solution 2:
- no afegeix ni
x-amz-security-token/ does not append evenx-amz-security-token - settings.py
AWS_STORAGE_BUCKET_NAME = 'mybucketname'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
- no afegeix ni
- Upgrading
to
Django 1.7. Getting error: Cannot serialize:
<storages.backends.s3boto.S3BotoStorage object
- Solució / Solution
- 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 ... importNewEmailMessage
...
@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)
- .py
- Django-compressor: how to write to S3, read from CloudFront?
- Instal·lació / Installation
- Amazon
Cloudfront
- Card
store
project #4: Notes on using Amazon's CloudFront
- create_bucket.py
- update_s3.py
- Moving to the clouds for a Django sites: Amazon S3 & Cloud Front vs. Rackspace Cloud Files & Akamai
- S3 and Cloudfront for Static and Uploaded files on Django (works on Heroku)
- Hosting Django static images with Amazon Cloudfront (CDN) using django-static
- Card
store
project #4: Notes on using Amazon's CloudFront
http://www.francescpinyol.cat/django.html
Primera versió: / First version: 18.XII.2012
Darrera modificació: 7 de juliol de 2026 / Last update: 7th July
2026