Django
|
Índex
|
|
|
- Django
- Alternatives
- Instal·lació / Installation
- using pyenv
- install pyenv
- cd ~/src
- create virtualenv:
pyenv virtualenv 3.10 mysite-3.10
- temporarily use created virtualenv
- install django
- create django project (estructura de directoris)
- set pyenv permanently:
cd mysite
pyenv local mysite-3.10
- Install Python
- optionally: PyDev
(Eclipse plugin)
- Install Django
- one of the following:
- from linux distribution packages
- easy_install:
easy_install --uprade django
- pip
- virtualenv + pip
- system-wide
# virtualenv /opt/PYTHON27
# source /opt/PYTHON27/bin/activate
- project-based
cd my_project
virtualenv venv
source venv/bin/activate
# pip install Django
# pip install Django==1.7.7
# deactivate
- it will be installed on:
/usr/lib/python2.7/site-packages/django/
- or, if using virtualenv:
/path/to/your/venv/lib/python2.X/site-packages/django/
- other modules, using virtualenv:
- virtualenv
- Biblioteques
addicionals / Additional libraries
- Documentació / Documentation
- Release
notes
- Django x
-
Django 1.8 |
Django >= 1.10 |
mydir/myproject/settings.py
-
INSTALLED_APPS = [
"polls",
]
|
mydir/myproject/settings.py
-
INSTALLED_APPS = [
"polls.apps.PollsConfig",
]
|
urls.py
|
mydir/myapp/urls.py
|
|
mydir/myapp/views.py
from .models import Question
|
|
|
- ...
- Django 4
-
Django < 4 |
Django 4 |
NullBooleanField() |
BooleanField(null=True) |
|
|
- ...
- Django 3
-
Django < 3 |
Django 3 |
from django.utils import six |
import six |
from django.shortcuts import
render_to_response
|
(removed)
use render() with request as additional
parameter
|
ModuleNotFoundError: No module named
'moneyed.localization'
- ...
- Django 2
- Django
2.0 release notes
- on_delete
for related fields is now mandatory
- on_delete
is a required positional argument for
ForeignKeys, even in migrations
- on_delete in migrations
- option 1: modify all migration files
(using perl and lookahead regex)
perl -pi.bak -e
's/models.ForeignKey\((?!.*on_delete)/models.ForeignKey\(on_delete=models.deletion.CASCADE,
/g' myapp/migrations/*.py
perl -pi.bak -e
's/models.OneToOneField\((?!.*on_delete)/models.OneToOneField\(on_delete=models.deletion.CASCADE,
/g' myapp/migrations/*.py
- option 2:squash migrations
- squash
your migrations
- add explicit on_delete in related
fields (ForeignKey, OneToOneField) (in migration
squashed file and models.py)
on_delete=models.CASCADE #
default value, no migration will
be created
- delete old migrations (when migrations
have been applied to all your instances)
- settings.MIDDLEWARE_CLASSES ->
settings.MIDDLEWARE (introduced in 1.10)
ImportError: cannot import name 'allow_lazy'
from 'django.utils.functional'
AttributeError: module
'django.contrib.gis.db.models' has no attribute
'GeoManager'
ModuleNotFoundError: No module named
'django.core.urlresolvers'
<class 'myapp.admin.MyModelAdmin'>:
(admin.E012) There are duplicate field(s) in
'fieldsets[15][1]'.
- django.core.exceptions.ImproperlyConfigured:
Specifying a namespace in include() without
providing an app_name is not supported. Set the
app_name attribute in the included module, or pass a
2-tuple containing the list of patterns and app_name
instead.
-
|
Django 1 |
Django 2 |
|
Simplified
URL routing syntax |
from django.conf.urls import include, url
urlpatterns = [
url(r'^admin/',
include(admin.site.urls)), |
from django.conf.urls import include
from django.urls import path
urlpatterns = [
path('admin/',
admin.site.urls), |
|
|
url(r'^rest-auth/',
include('dj_rest_auth.urls')), |
path('rest-auth/',
include('dj_rest_auth.urls')), |
|
|
url(r'^mypath/', include('myapp.urls',
namespace='mynamespace')), |
path('mypath/',
include('myapp.urls')),
and myapp/urls.py:
app_name = "mynamespace"
|
django.core.exceptions.ImproperlyConfigured:
Specifying a namespace in include()
without providing an app_name is not
supported.
Set the app_name attribute in the included
module,
or pass a 2-tuple containing the list of
patterns and app_name instead. |
tracker |
super(MyClass, self).save(*args, **kwargs)
my_field_has_changed =
self.tracker.has_changed('my_field') |
my_field_has_changed =
self.tracker.has_changed('my_field')
super(MyClass, self).save(*args, **kwargs)
# this would return False:
#my_field_has_changed =
self.tracker.has_changed('my_field') |
|
|
user.is_anonymous()
user.is_authenticated() |
# from Django 1.10
user.is_anonymous
user.is_authenticated |
|
|
dataandfiles.data._iterlists() |
dataandfiles.data.lists() |
|
|
# allow_lazy is
deprecated from Django 1.10
from django.utils.functional import
allow_lazy
def my_function(...)
my_function = allow_lazy(my_function,
six.text_type, SafeText) |
from django.utils.functional import
keep_lazy
def my_function(...)
my_function = keep_lazy(six.text_type,
SafeText)(my_function) |
|
- Problemes / Problems
Error: database connection isn't set to
UTC
- Solució / Solution
pip install 'psycopg2<2.9'
- ImportError:
Module "django.contrib.auth.middleware" does
not define a "SessionAuthenticationMiddleware"
attribute/class
TypeError: Direct assignment to the forward
side of a many-to-many set is prohibited. Use
mymodel.set() instead.
ValueError: callable 'xxx' is not supported
by signature
- when using allow_lazy
instead of keep_lazy (with different syntax)
TypeError: __init__() got an unexpected
keyword argument 'name'
- ...
- Biblioteques / Libraries
-
|
|
|
1.x |
2.x |
3.x |
4.x |
|
rtd |
git |
1.11 |
2.2 |
3.2 |
|
dj-rest-auth |
|
|
1.1.0 |
1.1.0
2.2.8
3.0.0 (rest_register: 204
instead of 201)
4.0.1 (rest_register: 204 instead of 201) |
6.0.0
master: registration/views.py |
x |
django-activity-stream |
|
|
0.6.3 |
0.10.0
1.4.1 |
2.0.0 |
1.4.0
2.0.0 |
django-admin-csvexport |
|
|
1.11 |
2.2 |
2.2 |
|
django-allauth |
|
x |
0.40.0 |
0.40.0
0.54.0 |
0.63.6 |
|
django-appconf |
|
|
1.0.3 |
1.0.3
1.0.6
- |
- |
|
django-autoslug |
|
|
1.9.8 |
1.9.8
1.9.9 |
1.9.9 |
|
django-axes |
|
|
5.3.3 |
5.3.3
5.27.0 |
6.4.0 |
|
django-cors-headers |
|
|
2.1.0 |
2.1.0
3.11.0 |
4.3.1 |
|
django-countries |
|
|
5.1.1 |
5.1.1
7.6.1 |
7.6.1 |
|
django-extra-fields |
|
|
2.0.5 |
2.0.5
3.0.2 |
3.0.2 |
|
django-filter |
|
|
2.2.0 |
2.2.0
21.1 |
23.5 |
|
django-ipware |
|
|
2.1.0 |
2.1.0
4.0.2
7.0.1 (?) |
- |
|
django-json-widget |
|
|
1.0.0 |
1.0.0
2.0.1 |
2.0.1 |
|
django-jsonfield-backport |
|
|
- |
1.0.5 |
- |
|
django-mass-edit |
|
|
3.2.0 |
3.2.0
3.5.0 |
3.5.0 |
|
django-model-utils |
|
|
3.0.0 |
3.0.0
4.0.0
4.1.0 (breaking changes)
4.2.0 |
4.5.1 |
|
django-modeltranslation |
|
|
0.14.4 |
0.14.4
0.18.2 |
0.18.11 |
|
django-modeltree |
|
|
- |
0.5 |
0.5 |
|
django-money |
|
|
2.0.3 |
2.0.3
3.5.3 |
3.5.2 |
|
django-nested-inline |
|
|
0.3.7 |
0.3.7
0.4.6 |
0.4.6 |
|
django-notifications-hq |
|
|
1.4.0
1.5 |
1.4.0
1.5.0
1.7.0 (depends on
django-model-utils>=3.1.0) |
1.8.3 |
|
django-otp |
|
|
0.9.4 |
1.1.6 |
1.5.0 |
|
django-paypal |
|
|
0.3.6 |
2.1 |
2.1 |
|
django-solo |
|
|
1.1.5 |
1.2.0
2.0.0 |
2.2.0 |
|
django-sslserver |
|
|
0.19 |
0.19
0.22 |
0.22 |
|
django-stdimage |
|
|
2.4.2 |
2.4.2
5.3.0
6.0.2 (ImportError: cannot import name
'Resampling' from 'PIL.Image' => needs
pillow>=9.5.0
This package has been deprecated in favor of
django-pictures. |
6.0.2 |
|
django-storages |
|
|
1.9.1 |
1.9.1
1.12.3 (bucket -> bucket_name) |
1.14.3 |
|
django-taggit |
|
|
1.3.0 |
1.3.0
1.5.1
2.1.0 (RelatedManager.set) |
4.0.0 |
|
djangorestframework |
|
|
3.11 |
3.11.0
3.13.1 |
3.15.1 |
|
djangorestframework-csv |
|
|
2.1.0 |
2.1.0
3.0.2 |
3.0.2 |
|
djangorestframework-gis |
|
|
0.15 |
0.15
1.0 |
1.0 |
|
djangorestframework-jwt |
|
|
1.10.0 |
1.10.0
1.11.0 |
1.11.0 |
|
drf-extensions |
|
|
0.4.0
0.5.0 |
0.4.0
0.7.1 |
0.7.1 |
|
drf-nested-routers |
|
|
0.91 |
0.91
0.93.4 |
0.94.0 |
|
drf-yasg |
|
|
1.17.1
1.20.0 |
1.17.1
1.21.7 |
1.21.7 |
|
- Django 1.10, 1.11
-
|
last version that
supports Django 1.11 |
notes |
django-allauth |
0.40.0 |
|
dj-rest-auth |
1.1.0 |
|
django-activity-stream |
0.8.0
0.6.3
|
0.8.0 is not enough for Django2: ModuleNotFoundError:
No module named 'jsonfield_compat' :
you will need 0.10.0 and install
django-jsonfield-backport |
django-autoslug |
1.9.8 |
|
django-axes |
5.3.3 |
File
"[...]/lib/python3.8/site-packages/axes/checks.py",
line 75, in axes_middleware_check
if
"axes.middleware.AxesMiddleware" not in
settings.MIDDLEWARE:
TypeError: argument of type 'NoneType' is
not iterable When upgrading to
django-axes v5:
WARNINGS:
?: (axes.W002) You do not have
'axes.middleware.AxesMiddleware' in your
settings.MIDDLEWARE.
?: (axes.W003) You do not have
'axes.backends.AxesBackend' in your
settings.AUTHENTICATION_BACKENDS.
HINT: AxesModelBackend was renamed to
AxesBackend in django-axes version 5.0.
|
django-filter |
2.2.0 |
Migration
guide (to 2.0); 2.0 is the minimum for
Django 2.x
- Filter.name -> Filter.field_name
- _0, _1 -> _after, _before; _min,
_max
- or overwrite widget, to keep using
_0, _1:
import
django_filters
class
OldRangeWidget(django_filters.widgets.RangeWidget):
suffixes =
["0", "1"]
class
MyIsoDateTimeRangeField(django_filters.fields.IsoDateTimeRangeField):
widget =
OldRangeWidget
class
MyIsoDateTimeFromToRangeFilter(django_filters.RangeFilter):
field_class
= MyIsoDateTimeRangeField
|
django-mass-edit |
3.2.0 |
|
django-money |
2.0.3 |
|
django-modeltranslation |
0.14.4 |
|
django-notifications-hq |
1.5 |
1.5: breaking changes |
django-solo |
1.2.0 |
|
django-stdimage |
|
|
django-storages |
1.9.1 |
|
django-taggit |
1.3.0 |
|
djangorestframework |
3.11 |
|
drf-extensions |
0.5.0 |
|
drf-nested-routers |
? |
|
drf-yasg |
1.20.0
? |
|
- settings
# even for DEBUG
ALLOWED_HOSTS = ['*']
#MIDDLEWARE_CLASSES = (...)
MIDDLEWARE = [...]
- namespaces must be unique
- patterns
- old style:
from
django.conf.urls import patterns, include,
url
urlpatterns = patterns('',
url(r'^', ...
)
- new style:
urlpatterns = [
url(r'^', ...
]
- deprecated: get_all_field_names
# LogEntry._meta.get_all_field_names()
[f.name for f in LogEntry._meta.get_fields()]
- django_filters
- "
__init__() got an unexpected keyword
argument 'request' "
- old
class
MyModelFilterSet(django_filters.FilterSet):
def __init__(self,
data=None, queryset=None, prefix=None,
strict=None):
- new
class
MyModelFilterSet(django_filters.FilterSet):
def __init__(self,
data=None, queryset=None, prefix=None,
strict=None,
request=None):
- Django 1.9
- Django
1.7 release notes
- Django
1.6 release notes
- Migració de python 2 a 3 /
Python 2 to 3 migration
- Python 3
- Issues
- Paquets / Packages
-
|
last version that
supports Python 2.7 |
comments |
Django |
1.11.29 |
|
django-allauth |
0.40.0 |
|
django-rest-auth |
0.9.5 |
should be replaced by dj-rest-auth (but it
does not support Python2) |
dj-rest-auth |
0.1.1 |
Not working on Python2
|
django-axes |
4.5.4 |
|
django-stdimage |
2.4.2 |
we cannot use StdImageFieldFile with
django-stdimage <4.1.0, because when
myinstance.refresh_from_db() is called
(e.g. explicitly from tests, implicitly from
api serializers), it raises an error:
"RuntimeError: maximum recursion depth
exceeded".
But django-stdimage >=4.1.0, even if it
is installed from python2.7, has python3
syntax (e.g. call to super without
parameters) |
django-storages |
1.9.1 |
|
django-taggit |
0.24.0 |
|
djangorestframework |
3.9.4 |
|
drf-extensions |
0.4.0 |
|
drf-nested-routers |
0.91 |
|
drf-yasg |
1.17.1 |
|
futures |
3.3.0 |
not needed by Python3; required by
google-... |
ruamel.ordereddict |
0.4.15 |
not needed by Python3 |
ruamel.yaml |
0.16.13 |
|
ruamel.yaml.clib |
0.2.2 |
|
... |
|
|
- ...
- Tutorials
- Django
documentation (1.6)
(1.5)
- First steps
- The
model
layer
- The view layer
- The template layer
- Forms
- The development process
- The admin
- Internationalization an location
- Python compatibility
- Performance and optimization
- Django
project optimization guide (part 1)
- Django logging, Django Debug Toolbar, Silk
- Profiling
- Info
- Django logging
- Django Debug Toolbar
- Silk
- settings.py
INSTALLED_APPS
= [
...
'silk',
...
]
MIDDLEWARE = [
...
'silk.middleware.SilkyMiddleware',
...
]
SILKY_PYTHON_PROFILER = True
- Load testing
- Throttling
- Geographic framework
- GeoDjango
- GeoDjango
installation
- With PostgreSQL
- Install and setup PostGis
- Python requirements
- Mageia
urpmi
postgresql9.4-devel
- CentOS
yum install
postgresql-devel
pip install psycopg2
- Problemes / Problems
- When filtering (GET), http server
response is: "
permission
denied for relation
spatial_ref_sys "
- Solution
- Connect to database as
root user:
- Mageia
psql
--username
postgres
your_django_db
- CentOS
sudo su -
postgres
psql
your_django_db
- grant permissions to
your_django_user
your_django_db=#
GRANT SELECT ON
spatial_ref_sys TO
your_django_user;
- Admin
- Tutorial
- Example
- settings.py
DATABASES = {
'default': {
#'ENGINE': 'django.db.backends.mysql',
'ENGINE':
'django.contrib.gis.db.backends.postgis',
'NAME': 'my_db',
'USER': 'my_user',
'PASSWORD':
open(os.path.join(BASE_DIR,
'db_p.txt'),'r').read().strip(),
'HOST':
'127.0.0.1',
#
Set to empty string for localhost.
'PORT':
'',
#
Set to empty string for default.
}
}
INSTALLED_APPS
= (
...
'django.contrib.gis',
)
- models.py
#from
django.db import models
from django.contrib.gis.db import
models
class MyModel(models.Model):
...
# geographical
coordinates
place =
models.PointField(_("Place"),
help_text=_("Place"), blank=True,
null=True)
# to allow geo
queries, even if geo fields are in a
OneToOneField
objects =
models.GeoManager()
#from
django.db import models
from django.contrib.gis.db import
models
class Location(models.Model):
"""
Geographical
location
"""
name =
models.CharField(_("Name"),
help_text=_("Location name"),
max_length=100, blank=True, null=True)
point =
models.PointField(_("Geographical
point"), help_text=_('In GeoJSON
format (e.g.: {"type":
"Point","coordinates":
[1.1099624632241494,41.15451486130159]})'),
blank=True, null=True)
class MyModel(models.Model):
...
# geographical
coordinates
location =
models.OneToOneField( Location,
verbose_name=_("Location"),
help_text=_("Location of the object"),
blank=True, null=True )
# to allow geo
queries, even if geo fields are in a
OneToOneField
objects =
models.GeoManager()
- admin.py
#from
django.contrib import admin
from django.contrib.gis import admin admin.site.register(MyModel)
#from
django.contrib import admin
from django.contrib.gis import admin
admin.site.register(Location,
admin.OSMGeoAdmin) admin.site.register(MyModel)
- serializers.py
class
MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = (..., 'place',)
from
rest_framework_gis.serializers import
GeoFeatureModelSerializer
class
LocationSerializer(GeoFeatureModelSerializer):
""" A class to
serialize locations as GeoJSON
compatible data """
class Meta:
model = Location
id_field = False
geo_field = 'point'
fields = ('name',)
class
MyModelSerializer(serializers.ModelSerializer):
location =
LocationSerializer(required=False)
def create(self,
validated_data):
if 'location' in validated_data:
# create the location object
location_data
= validated_data.pop('location')
location
=
Location.objects.create(**location_data)
# relate it to the object
validated_data['location']
= location
my_model =
MyModel.objects.create(**validated_data)
return my_model
class Meta:
model = MyModel
fields = (...,
'location',)
- Filters
-
database
|
distance_filter_convert_meters |
|
lat-lon
|
WGS84
|
SRID
4326
|
True
|
default
|
meters |
|
SRID
3875 (900913) |
False |
|
- views.py
class
MyModelViewSet(viewsets.ModelViewSet)
...
# filter
filter_backends =
(DistanceToPointFilter,)
# geo
distance_filter_field =
'my_field_containing_point'
# as distance in
filter is specified in meters and
database is in lat-lon (srid 4326),
conversion is needed
distance_filter_convert_meters = True
- Djangorestframework
- djangorestframework-gis
- Installation
pip install
djangorestframework-gis
- Example
- settings.py
INSTALLED_APPS
= (
...
'rest_framework',
'rest_framework_gis',
)
- Other core functionalities
- Middleware
- Using
Django
- django-admin
export
PYTHONPATH="/absolute/path/to/mysite:$PYTHONPATH"
django-admin ... --settings=mysite.settings
export
DJANGO_SETTINGS_MODULE=mysite.settings
django-admin ...
- django-admin and manage.py
- custom manage commands
- Writing
custom django-admin commands
- Exemple / Example
- my_app/management/commands/my_command.py
import
logging
from argparse import
RawTextHelpFormatter
from django.core.management.base
import BaseCommand
logger =
logging.getLogger(__name__)
class Command(BaseCommand):
def
create_parser(self, prog_name,
subcommand):
"""
Override create_parser, to add
formatter class that allows
newlines
"""
parser =
BaseCommand.create_parser(self,
prog_name, subcommand)
parser.formatter_class =
RawTextHelpFormatter
return parser
help = """
My command help
with newlines.
"""
def
add_arguments(self, parser):
parser.add_argument('--my-first-arg',
required=True,
help=("First argument"))
def
handle(self, *args, **options):
print("options:
{}".format(options))
my_first_arg_value =
options['my_first_arg']
# verbose -> logger level
# https://docs.djangoproject.com/en/dev/ref/django-admin/#cmdoption-v
# when not specified, default
value for options["verbosity"] is
1
logger_level = {
0: logging.ERROR,
1: logging.WARNING,
2: logging.INFO,
3: logging.DEBUG,
}.get(options["verbosity"],
logging.WARNING)
logger.setLevel(logger_level)
# sample logger messages
logger.error("[handle] error")
logger.warning("[handle] warning")
logger.info("[handle] info")
logger.debug("[handle] debug")
...
self.stdout.write(self.style.SUCCESS('Successfully
executed command'))
- call from command:
./manage my_app my_command
--my-first-arg='tata'
- call from unit test (How
to Unit Test a Django Management
Command):
from
django.core.management import
call_command
call_command("my_command",
"--my-first-arg=tata")
- ...
- Models
and databases
- ...
- Django snippets
- "How-to"
guides
- Trespams: Django
- contrib
packages
- Resum
- New project
- New application
cd my-project
python manage.py startapp my-app
- Estructura de directoris /
Directory layout (Django
1.4 release notes):
-
djcode_1.3
mysite
__init__.py
|
manage.py
|
toto.db
|
settings.py
|
urls.py
|
polls
__init__.py
|
models.py
|
tests.py
|
views.py
|
urls.py
|
|
|
|
djcode_1.4
mysite
manage.py
|
sqlite.db
|
mysite
|
polls
__init__.py
|
models.py
|
tests.py
|
views.py
|
urls.py
|
|
.project
|
.pydevproject
|
|
|
- project:
django-admin .py
startproject
mysite
- application:
python
manage.py
startapp polls
- Eclipse files (PyDev)
-
- Creació d'un projecte / Start a project (a project contains
several apps)
cd
[~/src/]djcode
- create the project ("mysite"):
- configuració de les
bases de dades /
database setup:
- SQLite
- PostgreSQL
- MariaDB / MySQL
- Creeu
un
usuari, una contrasenya i una base de dades /
Create user (
user_name ) and password
(password_for_user_name ) and a
database (database_name )
- option 1: manually
mysql -u root -p -h localhost
CREATE
DATABASE IF NOT EXISTS database_name;
GRANT ALL ON database_name.*
TO 'user_name'@'localhost'
IDENTIFIED
BY 'password_for_user_name';
FLUSH PRIVILEGES;
exit;
CREATE
DATABASE IF NOT EXISTS database_name;
GRANT ALL ON database_name.*
TO 'user_name'@'%'
IDENTIFIED
BY 'password_for_user_name';
FLUSH PRIVILEGES;
exit;
- option 2: by script
- mariadb_create.sh
#!/bin/bash
# add user and password
usuari=user_name
contrasenya=password_for_username
base_dades=database_name
mysql -u root -p <<EOF
CREATE DATABASE ${base_dades};
GRANT ALL ON ${base_dades}.* TO
'${usuari}'@'localhost' IDENTIFIED BY
'${contrasenya}';
FLUSH PRIVILEGES;
EOF
- Instal·leu
MySQL-python
/ Install MySQL-python
mysite/settings.py
-
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'database_name', 'USER': 'user_name', 'PASSWORD': 'password_for_user_name', 'HOST': '', # Set to empty string for localhost. 'PORT': '', # Set to empty string for default. } }
INSTALLED_APPS = (
...
)
- create the tables on the database (one table for each
app in INSTALLED_APPS list)
- Django >= 1.7
- Django < 1.7
python manage.py syncdb
- a més, si feu servir South
/ in addition, if you are using South:
python manage.py migrate --list
python manage.py migrate ...
- per a esborrar la base de dades i començar de nou / to
remove the database and start from scratch:
- ./esborra_base_dades.sh
#!/bin/bash
mysql -u root -p <<EOF
DROP DATABASE datbase_name;
EOF
- ./crea_base_dades.sh
#!/bin/bash
mysql -u root -p <<EOF
CREATE DATABASE database_name;
GRANT ALL ON database_name.* TO
'user_name'@'localhost' IDENTIFIED BY
'password_for_user_name';
FLUSH PRIVILEGES;
EOF
python manage.py syncdb
- administration:
mysite/settings.py
(activated by default in Django 1.6)
INSTALLED_APPS = (
...
'django.contrib.admin',
)
mysite/urls.py
(activated by default in Django 1.6)
from django.contrib import
admin
admin.autodiscover()
urlpatterns = patterns('',
...
url(r'^admin/',
include(admin.site.urls)),
)
- update the database:
- check that it is working:
- poll application administration:
- personalise the look and feel of the admin pages:
mysite/settings.py
TEMPLATE_DIRS
= (
"/absolute/path/to/djcode/mytemplates/"
)
# from django 1.6:
TEMPLATE_DIRS = [os.path.join(BASE_DIR,
'mytemplates')]
- copy the admin templates to
~/src/djcode/mytemplates/:
mkdir -p ~/src/djcode/mytemplates/admin
cp
/usr/lib/python2.7/site-packages/django/contrib/admin/templates/admin/base_site.html
~/src/djcode/mytemplates/admin/
cp
/usr/lib/python2.7/site-packages/django/contrib/admin/templates/admin/index.html
~/src/djcode/mytemplates/admin/
- edit the template
admin/base_site.html
(títol)
- edit the template
admin/index.html
(llistat d'aplicacions)
- create the first app ("polls"):
cd djcode/mysite
python manage.py startapp polls
- modify general files:
mysite/mysite /settings.py
INSTALLED_APPS = (
...
'polls',
)
mysite/ mysite /urls.py
...
urlpatterns = patterns('',
url(r'^polls/',
include('polls.urls')),
...
)
- create specific files:
- [check how the database will be created]:
python manage.py sql polls
python manage.py validate
python manage.py sqlcustom polls
python manage.py sqlclear polls
python manage.py sqlindexes polls
python manage.py sqlall polls
- create the polls tables into the database:
- other manage.py
functions:
- list of available functions
- call the API from command line:
- if you are using virtualenv:
python manage.py shell
(sets up the environment)
from my_app.models import *
python manage.py shell
<toto.py
- toto.py
from
my_app.models import *
m = MyModel.objects.filter(...)
print m
...
- Alternative
- How to execute
a Python script from the Django shell?
- standalone.py
# -*- coding:
utf-8 -*-
import django
from django.conf import settings
settings.configure()
django.setup()
...
# -*- coding:
utf-8 -*-
import sys, os, django
#sys.path.append('/path/to/my_project')
sys.path.append(os.getcwd())
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"my_project.settings")
django.setup()
# put your code here. E.g.:
from my_app.models import *
m = MyModel.objects.filter(...)
print m
...
python standalone.py
- or run standalone.py from Eclipse to debug
- create a (or several) superuser(s):
- browse the database:
- validate:
python manage.py validate
- inspect database:
python manage.py inspectdb
- ordres pròpies
/ custom commands
- Writing
custom django-admin commands
cd myproject/my_app
mkdir management
touch __init__.py
mkdir commands
touch __init__.py
my_command.py
from
django.core.management.base import
BaseCommand, CommandError
from my_app.models import MyModel
class Command(BaseCommand):
help = 'This is the
help text'
def handle(self,
*args, **options):
self.stdout.write('Successfully done')
myproject/env/bin/python manage.py
my_command
- Request
and
response objects
- Built-in
template
tags and filters
- How
to
access mod_ssl environment variables from django using
mod_wsgi?
- Javascript: window.onload
- service/template/main.html
{% block extrahead %}
<script type="text/javascript">
function carrega() { ... }
[...]
{% endblock %}
{%block
onload %}carrega(){% endblock %}
- Desplegament / Deployment
- Exemple mínim amb una vista
POST simple / Minimal example with a simple POST view:
- create dir for project
mkdir -p ~/src/myproject; cd ~/src/myproject
- install python3 in a virtualenv
virtualenv-3.5 env
source env/bin/activate
pip install --upgrade pip
- install django
- create django project:
django-admin startproject mysite .
- create application
./manage.py startapp myapp
- mysite/urls.py
from django.urls import
include, path
urlpatterns = [
path('myapp/',
include('myapp.urls')),
]
- myapp/urls.py
from django.urls import path
from . import views
app_name = 'myapp'
urlpatterns = [
path('toto/', views.toto,
name='toto'),
]
- myapp/views.py
from django.http import
HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def toto(request):
try:
primer =
request.POST['primer']
except:
return
HttpResponse("missing POST parameter: primer",
content_type="text/plain")
return HttpResponse("primer:
{0}".format(primer), content_type="text/plain")
./manage runserver
curl -i -X POST http://localhost:8000/myapp/toto/ -F
primer=2
- Exemples mínims / Minimal examples
-
|
|
project |
|
application |
|
|
|
|
access
|
|
manage |
settings |
urls.py |
application urls.py |
models |
admin |
application views.py |
html template |
./manage.py runserver |
hello world view |
- create dir
cd ~/src
mkdir -p mydir; cd mydir
- install python3 in a virtualenv
virtualenv env
source env/bin/activate
pip install --upgrade pip
- install django
- create django project:
django-admin startproject myproject
.
- create application
./manage.py startapp myapp
|
|
mydir/myproject/urls.py
from django.urls
import path, include
urlpatterns = [
path('myapp/',
include('myapp.urls')),
]
|
mydir/myapp/urls.py
from django.urls
import path
from . import views
urlpatterns = [
path('', views.index,
name='index'),
]
|
|
|
mydir/myapp/views.py
from django.http
import HttpResponse
def index(request):
return HttpResponse("Hello,
world. You're at the events index.")
|
|
- http://127.0.0.1:8000/myapp/
|
view with parameters from url |
- create dir
cd ~/src
mkdir -p mydir; cd mydir
- install python3 in a virtualenv
virtualenv env
source env/bin/activate
pip install --upgrade pip
- install django
- create django project:
django-admin startproject myproject
.
- create application
./manage.py startapp myapp
|
|
mydir/myproject/urls.py
from django.urls
import path, include
urlpatterns = [
path('myapp/',
include('myapp.urls')),
]
|
mydir/myapp/urls.py
from django.urls
import path
from . import views
urlpatterns = [
path('<int:question_id>/', views.detail,
name='detail'),
]
|
|
|
mydir/myapp/views.py
from django.http
import HttpResponse
def detail(request, question_id):
return HttpResponse("You're
looking at question %s." % question_id)
|
|
- http://127.0.0.1:8000/myapp/1/
- http://127.0.0.1:8000/myapp/2/
- ...
|
view with html template |
|
mydir/myproject/settings.py
(needed to find application templates)
INSTALLED_APPS = [
...
'myapp',
]
|
mydir/myproject/urls.py
from django.urls
import include, path
urlpatterns = [
path('myapp/',
include('myapp.urls')),
]
|
mydir/myapp/urls.py
from django.urls
import path
from . import views
urlpatterns = [
path('', views.index,
name='index'),
]
|
|
|
mydir/myapp/views.py
from django.shortcuts
import render
def index(request):
context = {'day_number': 4}
return render(request,
'myapp/index.html', context)
|
mydir/myapp/templates/myapp/index.html
- Today is: {{ day_number }}.
|
- http://127.0.0.1:8000/myapp/
|
post view |
- create dir
- cd ~src
mkdir -p mydir; cd mydir
- install python3 in a virtualenv
virtualenv env
source env/bin/activate
pip install --upgrade pip
- install django
- create django project:
django-admin startproject myproject
.
- create application
./manage.py startapp myapp
|
|
mydir/myproject/urls.py:
from django.urls
import include, path
urlpatterns = [
path('myapp/',
include('myapp.urls')),
]
|
mydir/myapp/urls.py:
from django.urls
import path
from . import views
app_name = 'myapp'
urlpatterns = [
path('toto/', views.toto,
name='toto'),
]
|
|
|
myproject/mysite/myapp/views.py:
from django.http
import HttpResponse
from django.views.decorators.csrf import
csrf_exempt
@csrf_exempt
def toto(request):
try:
primer = request.POST['primer']
except:
return HttpResponse("missing POST parameter:
primer", content_type="text/plain")
return
HttpResponse("primer: {0}".format(primer),
content_type="text/plain")
|
|
curl -i -X POST
http://localhost:8000/myapp/toto/ -F primer=2
|
calendar html view
(events
as an array) |
- create dir
cd ~/src
mkdir -p calendar_dir; cd
calendar_dir
- install python3 in a virtualenv
virtualenv env
source env/bin/activate
pip install --upgrade pip
- install django
- create django project:
django-admin startproject
calendar_project .
- create application
./manage.py startapp events
- create database
./manage makemigrations
./manage migrate
|
calendar_dir/calendar_project/settings.py
INSTALLED_APPS = [
...
'events',
]
|
calendar_dir/calendar_project/urls.py
from django.contrib
import admin
from django.urls import include, path
urlpatterns = [
path('admin/',
admin.site.urls),
path('events/',
include('events.urls')),
]
|
calendar_dir/events/urls.py
from django.urls
import path
from . import views
urlpatterns = [
path('', views.index,
name='index'),
path('calendar',
views.calendar, name='calendar'),
]
|
calendar_dir/events/models.py
from django.db import
models
class Event(models.Model):
id =
models.AutoField(primary_key=True)
name =
models.CharField(max_length=255,null=True,blank=True)
start =
models.DateTimeField(null=True,blank=True)
end =
models.DateTimeField(null=True,blank=True)
def __str__(self):
return self.name
|
calendar_dir/events/admin.py
from django.contrib
import admin
from .models import Event
admin.site.register(Event)
|
calendar_dir/events/views.py
from django.shortcuts
import render
from .models import Event
def calendar(request):
all_events =
Event.objects.all()
context = {
"events":all_events,
}
return
render(request,'events/calendar.html',context)
|
calendar_dir/events/templates/events/calendar.html
<html>
<head>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.css"/>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css"/>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"></script>
<script>
$(document).ready(function () {
var calendar = $('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
events: [
{% for event in events %}
{
title: "{{ event.name }}",
start: '{{ event.start|date:"c" }}',
end: '{{ event.end|date:"c" }}',
id: '{{ event.id }}',
},
{% endfor %}
],
selectable: true,
selectHelper: true,
editable: true,
eventLimit: true,
});
});
</script>
</head>
<body>
<br/>
<h2 align="center"><a
href="#">title</a></h2>
<br/>
<div class="container">
<div
id="calendar"></div>
</div>
</body>
</html>
|
- http://127.0.0.1:8000/admin
- http://127.0.0.1:8000/events/calendar
|
calendar html view
(events
as a json feed) |
|
|
|
urls.py
from django.urls
import path
from . import views
urlpatterns = [
path('calendar',
views.calendar, name='calendar'),
path('event_list',
views.event_list, name='event_list'),
]
|
|
|
views.py
import json
from datetime import datetime
from django.shortcuts import render
from django.http import HttpResponse
from .models import Event
def calendar(request):
return
render(request,'events/calendar.html')
def event_list(request):
start =
request.GET['start']
end = request.GET['end']
// when timezone is set to
UTC,
requests are of type:
// /event_list?start=2020-01-26T00%3A00%3A00Z&end=2020-01-27T00%3A00%3A00Z&timezone=UTC&_=1580059864316
start_datetime =
datetime.strptime(start, '%Y-%m-%dT%H:%M:%SZ')
end_datetime =
datetime.strptime(end, '%Y-%m-%dT%H:%M:%SZ')
events =
Event.objects.filter(start__range=(start_datetime,
end_datetime))
result = []
for event in events:
print("- event: {}".format(event.name))
result.append({
'id': event.id,
'title': event.name,
'start': event.start.isoformat(),
'end': event.end.isoformat()
})
return
HttpResponse(json.dumps(result),
content_type="application/json")
|
calendar.html
<html>
<head>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.css"/>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css"/>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.10.1/fullcalendar.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.10.1/locale/ca.js"></script>
<script>
$(document).ready(function () {
var calendar = $('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
timezone: 'UTC',
locale: 'ca',
events: 'event_list',
defaultView: 'agendaDay',
eventTimeFormat: 'H:mm', // uppercase
H for 24-hour clock
nowIndicator: true,
//slotLabelInterval: {hours: 2},
slotLabelFormat: 'H:mm',
selectable: true,
selectHelper: true,
editable: true,
eventLimit: true,
});
});
</script>
</head>
<body>
<br/>
<h2 align="center"><a
href="#">title</a></h2>
<br/>
<div class="container">
<div
id="calendar"></div>
</div>
</body>
</html>
|
|
library |
- create dir
cd ~src
mkdir -p library_dir; cd library_dir
- (install python2 in a virtualenv)
virtualenv -p /usr/bin/python2.7 env
- install python3 in a virtualenv
source env/bin/activate
pip install --upgrade pip
- install django
- create django project:
django-admin startproject
library_project .
- create application
./manage.py startapp library_app
|
library_dir/library_project/settings.py
INSTALLED_APPS = [
...
'library_app',
]
|
library_dir/library_project/urls.py
|
library_dir/library_app/urls.py:
|
library_dir/library_app/models.py
|
library_dir/library_app/admin.py
|
library_dir/libray_app/views.py
|
|
- http://127.0.0.1:8000/admin/
- http://127.0.0.1:8000/library_app/
|
|
MVC
|
model
|
serialization
|
view
|
urls
|
|
file
|
|
attributes
|
type
|
class
|
attributes
|
functions
|
HTML templates / renderers |
method
|
|
from
django.db import models
|
|
|
|
function-based
|
|
|
|
def detail(request, poll_id)
|
|
GET
|
url(r'^(?P<poll_id>\d+)/$',
'detail'),
|
|
|
|
|
|
|
|
|
POST |
...
|
forms.py
|
# non-ORM (not linked to a
model)
from django import forms
|
|
generic
class-based:
from django.views import generic
|
generic.edit.FormView
|
|
|
|
|
POST |
url(...
MyFormView.as_view()
), |
# ORM
(linked to a model)
from django import forms
|
class
Meta:
model = MyModel
fields = ...
...
|
generic.edit.CreateView
|
- model
- fields
- template_name
|
- template_name_suffix
- object
|
|
mymodel_form.html |
POST
|
url(...
MyModelCreateView.as_view() ), |
generic.list.ListView
|
|
|
mymodel_list.html |
GET |
url(r'^mymodels/$',
MyModelListView.as_view()), |
generic.detail.DetailView
|
|
|
mymodel_detail.html |
GET |
url(...
MyModelDetailView.as_view() ), |
generic.edit.UpdateView |
- template_name_suffix
- object
|
|
mymodel_form.html |
PUT,PATCH
|
url(...
MyModelUpdateView.as_view() ), |
generic.edit.DeleteView |
|
|
mymodel_confirm_delete.html |
DELETE
|
url(...
MyModelDeleteView.as_view() ), |
admin.py
|
from django.contrib import
admin
- admin.ModelAdmin
- admin.TabularInline
|
- model
- fields
- fieldsets
- readonly_fields
- list_display
- inlines
- actions
|
|
|
|
|
|
|
|
url(r'^admin/',
include(admin.site.urls)),
|
serializers.py
|
from
rest_framework
import serializers
- serializers.Serializer
- serializers.ModelSerializer
- serializers.HyperlinkedModelSerializer
|
class
Meta:
model
fields
exclude
read_only_fields
...
|
APIView:
(no need to be attached to a model)
from rest_framework.views import APIView |
APIView
|
- renderer_classes
- parser_classes
- authentication_classes
- throttle_classes
- permission_classes
- content_negotiation_class
|
|
- get_renderers()
- get_parsers()
- get_authenticators()
- get_throttles()
- get_permissions()
- get_content_negotiator()
(must be implemented by the derived class):
- post(self, request, *args, **kwargs)
- get(self, request, *args, **kwargs)
- get(self, request, *args, **kwargs)
- put(self, request, *args, **kwargs)
- patch(self, request, *args, **kwargs)
- delete(self, request, *args, **kwargs)
|
Renderers:
- JSONRenderer
- UnicodeJSONRenderer
- JSONPRenderer
- YAMLRenderer
- UnicodeYAMLRenderer
- XMLRenderer
- TemplateHTMLRenderer (Django)
- StaticHTMLRenderer
- HTMLFormRenderer
- BrowsableAPIRenderer
- MultiPartRenderer
- Custom renderers
|
(explicitly defined at
derived class)
- POST
- GET
- PUT
- PATCH
- DELETE
|
url(... MyAPIView.as_view()
), |
generics:
from rest_framework import generics
|
generics.GenericAPIView
|
- (attributes from APIView) +
- model
- queryset
- serializer_class
- lookup_field
- lookup_url_kwarg
- paginate_by
- paginate_by_param
- pagination_serializer_class
- page_kwarg
- filter_backends
|
|
|
|
|
generics.CreateAPIView |
|
post( request,... ) |
POST
|
url(...
MyModelCreateView.as_view() ), |
generics.ListAPIView |
|
get( request,... ) |
GET
|
url(r'^mymodels/$',
MyModelListView.as_view() ), |
generics.RetrieveAPIView |
|
get( request,... ) |
GET |
url(...
MyModelDetailView.as_view() ), |
generics.UpdateAPIView
|
|
put( request,... ) |
PUT
|
url(...
MyModelUpdateView.as_view()
), |
|
patch( request,... ) |
PATCH |
generics.DestroyAPIView |
|
delete( request,... ) |
DELETE |
url(...
MyModelDeleteView.as_view() ), |
...
|
|
|
...
|
|
viewsets:
from rest_framework import viewsets
|
viewsets.GenericViewSet
|
|
|
|
from
rest_framework.routers import DefaultRouter, SimpleRouter
router = SimpleRouter()
router.register(r'mymodels', views.MyModelViewSet,
base_name='mymodel')
urlpatterns = patterns('',
url(r'^', include(router.urls)),
)
|
viewsets.ModelViewSet
|
|
create( request,... )
|
POST |
|
list( request,... ) |
GET |
|
retrieve( request,... ) |
GET |
|
update( request,... ) |
PUT |
|
partial_update( request,... ) |
PATCH |
|
delete( request,... ) |
DELETE |
viewsets.ReadOnlyModelViewSet
|
|
list( request,... ) |
GET |
|
retrieve( request,... ) |
GET |
...
|
|
|
|
|
file |
|
attributes |
type |
class
|
attributes
|
functions |
HTML templates / renderers
|
method |
|
model |
serialization |
view |
urls |
-
|
|
Model
|
urls.py
|
View
|
|
|
models
|
forms
/
serializers
|
admin (predefined)
|
views (custom)
|
templates
|
|
|
forms
|
serializers
|
usage
|
|
|
- constrained to dealing with HTML
output, and form encoded input
|
- used by djangorestframework
- not constrained to dealing with HTML output, and
form encoded input
|
|
|
|
|
file
|
|
models.py
|
forms.py
|
serializers.py
|
|
admin.py
|
views.py
|
*.html
|
not
using
database
(simple POST)
|
|
-
|
from
django
import forms
class MyForm(forms.Form):
foo = forms.BooleanField(required=False)
bar = forms.IntegerField(help_text='Must be an
integer.')
baz = forms.CharField(max_length=32,
help_text='Free text. Max length 32 chars.') |
from
rest_framework
import serializers
class SnippetSerializer(serializers.Serializer):
...
|
|
|
def
contacte(request):
# if the form has been submitted,
we need to process the form data
if request.method=='POST':
# create a form instance and
populate it with data from the request:
formulari = MyForm(request.POST)
#
a form bound to the POST data
# check whether it is valid:
if formulari.is_valid():
# process the data
...
return
HttpResponseRedirect('/') # redirect after POST
# if a GET (or any other method), we'll create
a blank form
else:
formulari = MyForm()
#
unbound form
return render(request, 'contacte.html',{'formulari':formulari})
|
contacte.html:
...
<form action="/your-name/"method="post">
{% csrf_token %}
{{
formulari }}
input type="submit"
value="Submit"
/>
</form>
|
|
|
from
djangorestframework.views import View from
resourceexample.forms import MyForm
class AnotherExampleView(View):
form = MyForm
def get(self, request )
...
def post(self, request)
return "POST request
with content: %s" % (repr(self.CONTENT))
|
|
using
database
|
user
|
user
|
django.contrib.auth.forms. UserCreationForm |
from rest_framework
import serializers
class UserSerializer (serializers.HyperlinkedModelSerializer)
class Meta:
model =
User
fields =
('url', 'username', 'email', 'groups')
|
|
|
from
django.contrib.auth.forms import UserCreationForm
def nou_usuari(request):
if request.method=='POST':
formulari = UserCreationForm(request.POST)
if formulari.is_valid:
formulari.save()
return
HttpResponseRedirect('/')
else:
formulari = UserCreationForm()
return render_to_response('nou_usuari.html',{'formulari':formulari},
context_instance=RequestContext(request))
|
|
django.contrib.auth.forms. AuthenticationForm |
|
|
|
from
django.contrib.auth.forms import AuthenticationForm def
entrada(request):
if not request.user.is_anonymous():
return
HttpResponseRedirect('/zona_privada')
if request.method == 'POST':
formulari = AuthenticationForm(request.POST)
if formulari.is_valid:
usuari =
request.POST['username']
contrasenya
= request.POST['password']
compte = authenticate(username=usuari,
password=contrasenya)
if acces is
not None:
if compte.is_active:
login(request,compte)
return
HttpResponseRedirect('/zona_privada')
else:
return
render_to_response('compte_no_actiu.html',
context_instance=RequestContext(request))
else:
return render_to_response('usuari_incorrecte.html',
context_instance=RequestContext(request))
else:
formulari = AuthenticationForm()
return render_to_response('entrada.html',{'formulari':formulari},
context_instance=RequestContext(request))
|
|
custom
|
from
django.db
import models
class MyModel(models.Model):
titol = models.CharField(max_length=30)
nombre_pagines = models.IntegerField() |
from
django.forms
import ModelForm class MyModelForm(ModelForm)
class Meta:
model = MyModel |
|
from
django.conf.urls import patterns, url
from polls
import views
urlpatterns =
patterns('',
# ex: /polls/
url(r'^$', views.index, name='index'),
# ex: /polls/5/
url(r'^(?P<poll_id>\d+)/$',
views.detail, name='detail'),
# ex: /polls/5/results/
url(r'^(?P<poll_id>\d+)/results/$', views.results, name='results'),
# ex: /polls/5/vote/
url(r'^(?P<poll_id>\d+)/vote/$', views.vote,
name='vote'),
) |
class
MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
|
from django.shortcuts
import render,
get_object_or_404
from polls.models
import Poll
def index(request):
latest_poll_list
= Poll.objects.all().order_by('-pub_date')[:5]
context =
{'latest_poll_list': latest_poll_list}
return render(request, 'polls/index.html',
context)
def detail(request, poll_id):
poll =
get_object_or_404(Poll, pk=poll_id)
return render(request, 'polls/detail.html',
{'poll': poll})
...
|
- polls/mymodel_form.html
<form action="..." method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
- polls/index.html
{% if
latest_poll_list %}
<ul>
{% for
poll in
latest_poll_list
%}
<li><a
href="/polls/{{poll.id
}}/">{{poll.question }}</a></li>
{% endfor
%}
</ul>
{% else
%}
<p>No
polls are available.</p>
{% endif
%}
- polls/detail.html
- polls/results.html
- ...
|
from django.conf.urls
import patterns, url
from polls
import views
urlpatterns =
patterns('',
url(r'^$', views.MyModelCreateView.as_view(),
name='mymodel_create'),
url(r'^$', views.IndexView.as_view(),
name='index'),
url(r'^(?P<pk>\d+)/$',
views.DetailView.as_view(),
name='detail'),
url(r'^(?P<pk>\d+)/results/$',views.ResultsView.as_view(),
name='results'),
url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),
) |
Utilització
de vistes genèriques / Using generic views:
from django.views import
generic
from polls.models
import Choice, Poll
class MyModelCreateView(generic.CreateView):
model = MyModel
class IndexView(generic.ListView):
# if template_name is not specified, default
is:
# <app_name>/<model_name>_list.html
template_name =
'polls/index.html'
# if context_object_name is not specified,
default is:
# <model_name>_list
context_object_name =
'latest_poll_list'
def get_queryset(self):
"""Return the last
five published polls."""
return Poll.objects.order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
model =
Poll
# if
template_name is not specified, default is:
#
<app_name>/<model_name>_detail.html
template_name
= 'polls/detail.html'
class ResultsView(generic.DetailView):
model =
Poll
# if
template_name is not specified, default is:
#
<app_name>/<model_name>_detail.html
template_name
= 'polls/results.html'
def vote(request, poll_id):
... |
|
|
|
|
from rest_framework
import serializers
class ... (serializers.ModelSerializer)
|
|
|
|
|
|
|
models |
forms |
serializers |
urls.py |
admin |
views |
templates |
- ORM:
Object-relational mapping (wp)
- Models
- Polls
example models
- Model
Meta
options
- Manager
- create
- update
my_instance.save()
- only specified fields:
my_instance.save(update_fields=[...])
- delete
- Camps / Fields
- Model
field
reference
- Deprecation
- Index
- Validation
- Validators
- validate the range of an integer:
def validate_byte_char(value):
"""
Validate if a two-character
value is a valid byte in hexadecimal
"""
try:
int(value, 16)
except ValueError:
raise ValidationError('"%s" is not a valid
byte (00 .. ff)' % value)
class MyModel(models.Model):
my_byte =
models.CharField(max_length=2, validators=[validate_byte_char])
- validate the uniqueness of a true boolean in the
table:
class
MyModel(models.Model):
is_the_only_one
= models.BooleanField(default=False)
def clean(self):
from django.core.exceptions import
ValidationError
c =
MyModel.objects.filter(is_the_only_one__exact=True)
if
c and self.is_the_only_one:
raise
ValidationError({' is_the_only_one ':_("The
only
one is already present")})
- validate that the sum of two fields does not
exceed a maximum
class
MyModel(models.Model):
field_1 =
models.IntegerField()
field_2 =
models.IntegerField()
def clean(self):
from django.core.exceptions import
ValidationError
total = self.field_1 + self.field_2
if
total > settings.MAXIMUM_TOTAL:
# non-field error
raise
ValidationError(
_("The
sum of field_1 and field_2 cannot exceed %d" %
settings.MAXIMUM_TOTAL))
- simple (CharField, TextField, ...) ("verbose_name" es
pot ometre si és el primer atribut / can be ommited if
it is the first attribute)
class Toto(models.Model):
"""
General description of this
model/table.
"""
title =
models.CharField(_("Title"), help_text=_("Long
explanation for the title for this record"),
max_length=100 )
description = models.TextField( verbose_name= _("Description"),
help_text=_("Description
of the challenge"), default="Default text for the
description.")
second_ description
= models.TextField( verbose_name= _("Second
description"),
blank=True)
def __unicode__(self):
return
u'%s' % (self.title)
- FileField
- URLField
- URLField
- Extended for rtmp (CharField must be used instead
of URLField)
- models.py
from
django.core.validators import URLValidator
class MyModel(models.Model):
rtmp_url =
models.CharField(
validators=[URLValidator(schemes=['http','rtmp'])],
max_length=200 )
- UUIDField
- Choices
- Handle
choices
the right way
- Exemple / Example
- models.py
class
MyModel(models.Model):
FIELD_A_CHOICE1= 5
FIELD_A_CHOICE2 = 6
FIELD_A_CHOICE1_STR =
'fifth'
FIELD_A_CHOICE2_STR =
'sixth'
FIELD_A_CHOICES = (
(FIELD_A_CHOICE1,
FIELD_A_CHOICE1_STR),
(FIELD_A_CHOICE2,
FIELD_A_CHOICE2_STR),
)
field_a =
models.IntegerField(
choices=FIELD_A_CHOICES )
- get string key from integer value (e.g. from
another file):
from my_app.models
import MyModel
key = MyModel .FIELD_A_CHOICE1
string =
dict(mymodel.FIELD_A_CHOICES)[key] # will
return FIELD_A_CHOICE1_STR
- get integer value from string key:
def get_choice_value(choices,
data):
if data.isdigit():
return int(data)
else:
if isinstance(choices,
collections.OrderedDict):
# choices is already an OrderedDict
choices_ordered_dict = choices
else:
# choices is a tuple
choices_ordered_dict =
collections.OrderedDict(choices)
for key,val in
choices_ordered_dict.iteritems():
if val==data:
return key
return None
get_choice_value(MyModel.FIELD_A_CHOICES,
'sixth') # will return 6
- get a string with a list of all values,
comma-separated
', '.join([v for
k, v in MyModel.FIELD_A_CHOICES])
- Djangorestframework
- DateTimeField/
DateField / TimeField
- Omple automàticament quan es crea:
data =
models.DateField(auto_now_add=True)
- Omple automàticament quan s'actualitza:
data = models.DateField(auto_now=True)
- Timezone
- Data
/ Date
- Date in filter
- Date in
Python
- Django DateField
default options
- Dates in
DjangoRestFramework
- How
to Use Date Picker with Django
- TimeField
- models.py
class
MyModel(models.Model):
duration =
models.TimeField()
- admin.py
from
django.db import models
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.TimeField: {'widget': None},
}
- WeekdayField (django-weekday-field)
- pypi (1.1.0)
- bitbucket
(schinckel)
- github
(elpaso)
- utilització / usage
- models.py
from
weekday_field.fields import
WeekdayField
class MyModel(models.Model):
weekdays =
WeekdayField()
- admin.py
from
django.forms import
CheckboxSelectMultiple
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides
= {
WeekdayField: {'widget':
CheckboxSelectMultiple},
}
- DurationField (1.8)
- utilització / usage
- models.py
import
datetime
class MyModel(models.Model):
duration =
models.DurationField(default=datetime.timedelta(0))
- serializers.py (i18n version of standard
serializer of DurationField)
from
django.utils.translation import
ugettext_lazy as _
# django.utils.duration.py
def duration_string(duration):
"""i18n version of
str(timedelta)"""
days =
duration.days
seconds =
duration.seconds
microseconds =
duration.microseconds
minutes = seconds
// 60
seconds = seconds %
60
hours = minutes //
60
minutes = minutes %
60
string =
u'{:02d}:{:02d}:{:02d}'.format(hours,
minutes, seconds)
if days:
if days==1:
string = u'{} {}
'.format(days,_("day")) + string
else:
string = u'{} {}
'.format(days,_("days")) + string
if microseconds:
string +=
'.{:06d}'.format(microseconds)
return string
class
ModifiedDurationField(serializers.DurationField):
"""
DurationField
serializer which adds the i18n word
'day[s]'.
"""
def
to_representation(self, value):
return duration_string(value)
class
MyModelSerializer(serializers.ModelSerializer):
duration =
ModifiedDurationField( read_only=True
)
- JSONField
- introduced in Django
1.9
- it can be used with PostgreSQL >= 9.4
- default empty list
my_jsonfield = JSONField(... default=list,
blank=True)
- admin
- Trying
JSON in Django and PostgreSQL (and compare with
MongoDB)
- Problems
with json reencoding
- admin
- JSONEditorWidget
pip install
https://github.com/jmrivas86/django-json-widget/archive/master.zip
from
django.contrib.postgres.fields import
JSONField
from django_json_widget.widgets import JSONEditorWidget
class MyClassAdmin(admin.ModelAdmin):
# list
...
# detail
...
formfield_overrides = {
JSONField: {'widget': JSONEditorWidget}
}
formfield_overrides = {
JSONField: {'widget': JSONEditorWidget(height="300px")}
}
- PrettyJSONWidget
from
django.contrib.postgres.fields import
JSONField
from django.forms import widgets
class PrettyJSONWidget(widgets.Textarea):
def format_value(self,
value):
try:
value = json.dumps(json.loads(value),
indent=2, sort_keys=True)
# these lines will try to adjust size of
TextArea to fit to content
row_lengths = [len(r) for r in
value.split('\n')]
self.attrs['rows'] =
min(max(len(row_lengths) + 2, 10), 30)
self.attrs['cols'] =
min(max(max(row_lengths) + 2, 40), 120)
return value
except Exception as e:
return super(PrettyJSONWidget,
self).format_value(value)
class MyClassAdmin(admin.ModelAdmin):
# list
...
# detail
...
formfield_overrides = {
JSONField: {'widget': PrettyJSONWidget}
}
- PostgreSQL
specific model fields
- ArrayField
- example
from
django.contrib.postgres.fields import
ArrayField
my_array_field = ArrayField(...)
- default empty list
my_arrayfield = ArrayField(...
default=list, blank=True)
- Relations
one to one
|
foreign
key |
generic foreign key |
many to many
|
- one ... can have only one ...
|
- one manufacturer (only Manufacturer
objects) can have several cars
|
- one bookmark or one note can have several
tags
|
- several ... can have several ...
|
|
|
|
|
direct |
inverse |
through |
|
class Manufacturer(models.Model):
"""
A manufacturer consists of
usual fields, and 0 or more cars
"""
# usual fields |
class Bookmark(models.Model):
"""
A bookmark consists of a
URL, and 0 or more descriptive tags.
"""
# usual fields
url = models.URLField()
# generic relation
tags = GenericRelation(TaggedItem)
|
class Note(models.Model):
"""
A note consists of some
text, and 0 or more descriptive tags.
"""
# usual fields
text =
models.CharField(max_length=1000)
# generic relation
tags = GenericRelation(TaggedItem) |
|
|
|
|
class Car(models.Model):
# usual fields
# foreign key
manufacturer = models.ForeignKey(
Manufacturer,
#
van be 'Manufacturer' (name) if not defined
yet
on_delete=models.CASCADE,
related_name = "cars",
) |
from django.db import models
from django.contrib.contenttypes.fields import
GenericForeignKey
from django.contrib.contenttypes.models import
ContentType
class TaggedItem(models.Model):
# usual fields:
...
# three parts to setup a
GenericForeignKey:
content_type =
models.ForeignKey(ContentType)
object_id =
models.PositiveIntegerField()
content_object = GenericForeignKey('content_type',
'object_id') |
|
|
|
- ForeignKey
- one model can be attached to only one kind of
model
class Toto(models.Model):
user = models.ForeignKey(MyUser,
verbose_name=_("User"), help_text=_("This is an
extended help"), related_name='myuser')
- Admin
ForeignKey
- related_name
- on_delete
- What
does adding on_delete to models.py do, and
what should I put in it? [duplicate]
- mandatory from Django
2
- when parent instance is deleted, also delete
child instance (was default on Django<2):
class
ChildModel(models.Model):
parent =
models.ForeignKey(ParentModel,
on_delete=models.CASCADE)
- when parent instance is deleted, do not delete
child instance:
class
ChildModel(models.Model):
parent =
models.ForeignKey(ParentModel,
on_delete=models.SET_NULL,
null=True)
- GenericForeignKey
- one model can be attached to several kind of
models
- Admin
GenericForeignKey
- Avoid
Django's GenericForeignKey
- Generic
relations (The
contenttypes
framework)
- How
to Use Django's Generic Relations
- How
to use GenericForeignKey in Django
- on_delete
- Reverse
generic relations
- "Unlike ForeignKey, GenericForeignKey does
not accept an on_delete argument
to customize this behavior; if desired, you
can avoid the cascade-deletion by not using
GenericRelation, and alternate behavior can
be provided via the pre_delete signal."
- ContentType
- Get model from content type:
my_content_type.model_class()
- Get content type from model (or instance):
ContentType.objects.get_for_model(MyModel)
- Serialization with djangorestframework
- Example (see also table above):
from django.db import
models
from django.contrib.contenttypes.fields import
GenericForeignKey
from django.contrib.contenttypes.models import
ContentType
class TaggedItem(models.Model):
# usual fields:
...
# three parts to setup a
GenericForeignKey:
content_type =
models.ForeignKey(ContentType)
object_id =
models.PositiveIntegerField()
content_object = GenericForeignKey('content_type',
'object_id')
def
__str__(self):
#
__unicode__ on Python 2
return self.tag
class
Bookmark(models.Model):
"""
A bookmark consists of a
URL, and 0 or more descriptive tags.
"""
url = models.URLField()
# IMPORTANT: adding
GenericRelations forces
on_delete=models.CASCADE:
# when a Bookmark
object is deleted, all tags are also deleted
# If this GenericRelation
was not specified, when a Bookmark object was
deleted, all related tags
# would not be deleted and
would remain pointing to a non existing object
(defined by a pair of content_type/object_id)
tags = GenericRelation(TaggedItem,
related_query_name="bookmarks")
# implies default names for
fields:
# tags = GenericRelation(TaggedItem,
related_query_name="bookmarks",
content_type_field="content_type",
object_id_field="object_id")
class Note(models.Model):
"""
A note consists of some
text, and 0 or more descriptive tags.
"""
text =
models.CharField(max_length=1000)
# IMPORTANT: adding
GenericRelations forces
on_delete=models.CASCADE:
# when a Note
object is deleted, all tags are also deleted
tags = GenericRelation(TaggedItem
,
related_query_name="notes" )
- my_bookmark
= Bookmark.objects.create(...)
my_first_bookmark_tag =
TaggedItem.objects.create(content_object=my_bookmark,
...) bookmark_from_tag
= my_first_bookmark_tag.bookmarks.first() my_bookmark_tags
= my_bookmark.tags.all()
- my_note
= Note.objects.create(...) my_first_note_tag
=
TaggedItem.objects.create(content_object=my_note,
...)
note_from_tag = my_first_note_tag.notes.first()
my_note_tags =
my_bookmark.tags.all()
- Problemes / Problems
- FieldError: Field 'content_object' does not
generate an automatic reverse relation and
therefore cannot be used for reverse querying.
If it is a GenericForeignKey, consider adding a
GenericRelation.
- OneToOneField
import django.db.models
class Tata(models.Model):
# on_delete per a no tenir
cascade
toto_field =
models.OneToOneField(Toto, blank=True, null=True,
on_delete=django.db.models.SET_NULL)
-
|
models.py
|
admin
|
djangorestframework
|
|
|
|
|
|
drf-swagger
|
hierarchical
|
|
separate
window
|
nested
serializer
|
nested
serializer appears as a string (?)
|
flat
|
not use
inheritance
|
|
flattened at
serializer
|
|
use multiple
inheritance
|
|
|
|
- OneToOneField
and
Deleting
from
django.db.models.signals import post_delete
from django.dispatch.dispatcher import
receiver
@receiver(post_delete, sender=Profile)
def post_delete_user(sender, instance, *args,
**kwargs):
if instance.user: # just in
case user is not specified
instance.user.delete()
- Admin
- Djangorestframework
- ManyToManyField
- Many-to-many
relationships
- type
- direct
class
Topping(models.Model):
# ...
class Pizza(models.Model):
# ...
toppings =
models.ManyToManyField(Topping)
- through
(allows extra fields for the connection; to
query for these extra fields, add the same
related_name to both foreign keys in the through
model) (see: admin)
- Admin
- ManyToMany
with
through in DjangoRestFramework
- Exemple / Example
class Person(models.Model):
name
= models.CharField(max_length=128)
def
__unicode__(self):
return self.name
class Group(models.Model):
name
= models.CharField(max_length=128)
members
= models.ManyToManyField(Person, through='Membership')
def
__unicode__(self):
return self.name
class Membership(models.Model):
person
= models.ForeignKey(Person,
related_name='membership')
group
= models.ForeignKey(Group,
related_name='membership')
date_joined
= models.DateField()
- symmetrical
- do
something
when the manytomany relation changes
- Serializer: aniuat
i filtrat / nested and filtered (ManyToMany)
- Queries:
query with a condition in ManyToMany
- Encrypted fields
- ...
- Impressió / Print
- How
do you serialize a model instance in Django?
- Some fields as a string with newlines
from django.forms.models
import model_to_dict
class MyModel():
field1 = ...
field2 = ...
...
def summary(self):
d =
model_to_dict(self, fields=('field1','field2',) )
return
u'\n'.join([u'{}: {}'.format(k,v) for k,v in
d.iteritems()])
- Senyals / Signals
- Excepcions / Exceptions
- IntegrityError
- Problem with atomic transactions
- To avoid message "An error occurred in the
current transaction. You can't execute queries
until the end of the 'atomic' block.":
- protect with transaction.atomic() (Controlling
transactions explicitly):
from django.db
import IntegrityError, transaction
@transaction.atomic
def viewfunc(request):
create_parent()
try:
with transaction.atomic():
generate_relationships()
except
IntegrityError:
handle_exception()
add_children()
- ...
- Grafs / Graphs
- Django - Model graphic
representation (ERD)
- django-extensions
- docs
- instal·lació / installation
pip
uninstall pyparsing
pip
install pyparsing==1.5.7
pip
install pydot
pip install "django-extensions<3"
"Django<2"
- settings.py
INSTALLED_APPS = (
...
'django_extensions',
...
)
- Ús / Usage
runscript
show_urls
- show all endpoints and reverse
...
- Graph
models
- instal·lació / installation
- utilització / usage
python manage.py graph_models my_app
| dot -Tpdf > my_app.pdf
- django-graphviz
- Dades inicials / Initial data
- Sites
- The
sites
framework
- activate it (Django >=1.6)
- settings.py
- INSTALLED_APPS = (
'django.contrib.sites',
)
- SITE_ID = 1
- python manage.py migrate
- Modify default value (example.com ->
first.example.org, second.example.org)
- Django Sites
Framework: Initial Data Migration Location
- steps
- settings.py
MIGRATION_MODULES
= {
'sites':
'my_project.migrations.sites',
}
python manage.py makemigrations
sites
- will create
my_project/migrations/sites/ 0001_initial.py
python manage.py makemigrations
--empty sites
- will create
my_project/migrations/sites/ 0002_auto_yyyymmdd_hhmm.py
- edit generated
0002_auto_yyyymmdd_hhmm.py
# -*- coding:
utf-8 -*-
from __future__ import
unicode_literals
from django.db import models,
migrations
def
insert_sites(apps, schema_editor):
"""Populate
the sites model"""
Site
= apps.get_model('sites', 'Site')
Site.objects.all().delete()
Site.objects.create(domain='first.example.org',
name='first_site')
Site.objects.create(domain='second.example.org',
name='second_site')
class Migration(migrations.Migration):
dependencies = [
('sites', '0001_initial'),
]
operations = [
migrations.RunPython(insert_sites)
]
- get the current site
- with request
current_site =
get_current_site(request)
- without request
from django.contrib.sites.models
import Site
current_site = get_current_site(request)
- Exemple: sobreescriu el mètode de desar, per a poder-hi
afegir una acció prèvia (per exemple, crear un camp calculat) /
Example: override the save method to add an action before it
(e.g. create a calculated
field)
- models.py
class Toto(models.Model):
def save(self, *args,
**kwargs):
# put
some actions here:
# then
call the parent save:
super(Toto, self).save(*args, **kwargs)
- Example: m2m
- Escriptura i lectura
a/des de la base de dades / Writing and reading to/from
database
- Escriptura: un model s'escriu a la base de dades quan
passa per django/db/transaction.py:
__exit__().connection_commit()
- des de l'admin això no es fa fins que es retorna la
petició; hi pot haver comportaments estranys si mentre
encara no s'ha desat l'objecte es fa una petició a la base
de dades (per exemple una petició a un endpoint disparada
dins del save o del post_save)
- Queries
- Making
queries
- QuerySet
API
-
|
math notation
|
django <
1.11
|
django >=
1.11
|
Union
|
|
qs = qs1 |
qs2
|
union()
|
Intersection
|
|
- qs1 & qs2
- filter()
- exclude()
|
intersection()
|
Difference
|
qs1 \ qs2
|
qs1.exclude(pk__in=qs2)
|
difference()
|
Empty
|
|
qs
= MyModel.objects.none() |
- union()
- Problemes / Problems
ORDER BY term does not match any
column in the result set
- Solució / Solution
- dynamically build Q expression (if
it applies):
from
django.db.models import Q
q_expression = Q(...)
for ...:
q_element =
Q(...)
q_expression |= q_element
qs =
MyModel.objects.filter(q_expression)
- Exemples / Examples
- .filter()
- Aggregation
(Aggregation
functions)
- Aggregate
in django with duration field
- aggregate:
- returns a dictionary with one or more
pair key/value (average, sum, custom...)
- Exemples / Examples
>>> from
django.db.models import Avg
>>>
Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}
>>> from
django.db.models import F,
FloatField, Sum
>>>
Book.objects.all().aggregate(
...
price_per_page=Sum(F('price')/F('pages'),
output_field=FloatField()))
{'price_per_page':
0.4470664529184653}
- annotate:
- returns a queryset, where each object
has a newly created virtual field
- Exemples / Examples
>>> from
django.db.models import Count
>>> pubs =
Publisher.objects.annotate(num_books=Count('book'))
>>> pubs
<QuerySet [<Publisher:
BaloneyPress>, <Publisher:
SalamiPress>, ...]>
>>> pubs[0].num_books
73
- filtrar pel nombre d'objectes
relacionats / filter by number of
related objects
from
django.db.models import Count
MyModel.objects.annotate(num_items=Count("relitems")).filter(num_items__gte=1)
- Django filter the
model on ManyToMany count?
from django.db.models import Count
myobjects =
MyModel.objects.annotate(number_related_objects=Count('related_objects')).filter(number_related_objects__gt=0)
myobjects =
MyModel.objects.filter(related_objets__is_valid=True).distinct()
- Tuples
- Django
filter queryset on “tuples” of values for
multiple columns
- Exemples / Examples
- given the following records ... :
- user: None, field1: a, field2: b,
...
- user: None, field1: c, field2: d,
...
- user: None, field1: e, field2: f,
...
- user: some_user, field1: c,
field2: d, ...
- user: some_other_user, field1: c,
field2: d, ...
- ... return the union of anonymous
records and some_user records,
considering that intersection is defined
as those records that have the same
tuple field1/filed2 (if some_user
defines the tuple c/d, get the entry
instead of the anonymous one):
- user: None, field1: a, field2: b,
...
- user: None, field1: e, field2: f,
...
- user: some_user, field1: c,
field2: d, ...
import
operator
from django.db.models import Q
first_qs =
MyModel.objects.filter(user=some_user)
first_qs_f1_f2 =
first_qs.values_list('field1','field2',)
intersection_taking_f1_f2 = reduce(
operator.or_,
(Q(verb=user_verb,
channel=user_channel) for f1, f2 in first_qs_f1_f2
)
)
second_qs =
MyModel.objects.filter(user=None).exclude( intersection_taking_f1_f2 )
final_qs = first_qs | second_qs
- .complex_filter()
- .exclude()
- __in=
- order_by
- F() expressions (1.7,
1.8)
- Filters can reference fields on the model (1.7,
1.8)
- Complex
lookups
with Q objects
from django.db.models import Q
- Q()
- NOT
- OR
- AND
- GCM
devices belonging to people with a given name and
joining day before today minus 5 days:
gcm_devices =
GCMDevice.objects.filter(person__in=(
Person.objects.filter(name=given_value,membership__date_joined__lt=datetime.date.today()-datetime.timedelta(days=5)
)
) )
- Dates
- Django: Using F
arguments in datetime.timedelta inside a query
- Hybrid (filter/Python):
# local time:
now = datetime.datetime.now()
now_date = now.date()
now_time = now.time()
now_weekday = now.weekday()
# Django query
# get all valid events
events = Event.objects.filter(
start_date__lte =
now_date,
end_date__gte =
now_date,
weekdays__contains =
now_weekday
)
# refine query in Python
# check if an event is active now
active_events = []
for event in events:
datetime_start =
datetime.datetime.combine(now_date,
event.start_time)
duration =
event.duration
datetime_end =
datetime_start + datetime.timedelta(
hours=duration.hour,
minutes=duration.minute,
seconds=duration.second )
print '%s: %s - %s' %
(event.name, datetime_start, datetime_end)
if (datetime_start
<= now) and (datetime_end >= now):
active_events.append(event)
print active_events
- one field "registration_id" from a queryset:
list_fields = GCMDevice.objects.values_list('registration_id',flat=True).filter(active=True)
devices = GCMDevice.objects.filter(active=True)
list_fields = list(disp.registration_id for disp
in devices )
- multiple fields "registration_id", "user__username" from a
queryset:
list_multiple_fields =
GCMDevice.objects.values_list('registration_id','user__username').filter(active=True)
- Django (web framework): How
do
you query with a condition on a ManyToMany model in
Django?
- add "related_name" in
ForeignKeys in through model
- How do I sort my Django Query
Set against today date?
- URLs
- URL
dispatcher (1.6)
- Django:
How
to Retrieve Query String Parameters
- Absolute path
- reverse
from django.core.urlresolvers import reverse
- get a list of all endpoints and all reverse values:
- reverse als test
unitaris
- simple
- with namespace
- my_project/my_project/urls.py
urlpatterns = [
url(r'^v1/', include('my_app.urls',
namespace='my_namespace')),
]
- my_project/my_app/urls.py
urlpatterns = [
url(r'^my_view/$',
views.MyView.as_view(), name='myview' ),
]
- toto.py
from django.urls
import reverse
url = reverse(' my_namespace: myview')
- with ViewSet and namespace
- my_project/my_project/urls.py
urlpatterns = [
url(r'^v1/', include('my_app.urls',
namespace='my_namespace')),
]
- my_project/my_app/urls.py
from rest_framework
import routers
from my_app import views
my_router = routers.SimpleRouter()
my_router.register(r'mymodels',
views.MyModelViewSet )
urlpatterns = [
url(r'^', include(my_router.urls)),
]
- toto.py
from django.urls
import reverse
url_list = reverse(' my_namescape: mymodel-list')
url_detail = reverse(' my_namescape: mymodel-detail',
kwargs={'...':'...'})
- rest_auth
- my_project/my_project/urls.py
urlpatterns = [
# rest-auth
url(r'^rest-auth/',
include('rest_auth.urls')),
url(r'^rest-auth/registration/',
include('rest_auth.registration.urls')),
url(r'^rest-auth/facebook/$',
FacebookLogin.as_view(), name='fb_login'),
]
- toto.py
reverse('rest_password_reset_confirm')
reverse('rest_password_reset_confirm')
reverse('rest_login')
reverse('rest_logout')
reverse('rest_password_change')
reverse('rest_register')
reverse('rest_password_reset')
reverse('rest_user_details')
reverse('rest_verify_email')
reverse('fb_login')
reverse('tw_login')
reverse('tw_login_no_view')
reverse('tw_login_no_adapter')
- admin
-
|
|
url
|
parameters
|
example
|
|
|
|
|
python
|
template
|
|
|
|
|
|
context = {}
# opts may be used by {% url opts|...
context["opts"] =
self.model._meta # needed by admin_urlname
in template |
AdminSite
|
Index
|
index
|
|
reverse("admin:index") |
<a
href="{% url 'admin:index'
%}">Home</a> |
Logout
|
logout
|
|
|
|
Password
change
|
password_change
|
|
|
|
Password
change done
|
password_change_done |
|
|
|
i18n
Javascript
|
jsi18n
|
|
|
|
Application
index page
|
app_list
|
app_label
|
reverse("admin:app_list",
kwargs={"app_label":
"myapp"}) |
|
Redirect to
object's page
|
view_on_site
|
content_type_id,
object_id
|
|
|
ModelAdmin
|
Changelist
(list of all instances of a given
model)
|
{{ app_label
}}_{{ model_name }}_changelist |
(see
ModelAdmin.changelist_view) |
reverse('admin:myapp_mymodel_changelist')
reverse( 'admin:%s_%s_changelist'
% self.get_model_info(),
current_app=self.admin_site.name)
|
|
Add
|
{{ app_label
}}_{{ model_name }}_add |
|
|
|
History
|
{{ app_label
}}_{{ model_name }}_history |
object_id |
|
|
Delete
|
{{ app_label
}}_{{ model_name }}_delete
|
object_id
|
|
|
Change
|
{{ app_label
}}_{{ model_name }}_change
|
object_id
original (is the instance)
... (see ModelAdmin.render_change_form) |
-
from
django.utils.safestring import
mark_safe
url = reverse("admin:myapp_mymodel_change",
args=[my_object_id])
self.message_user(
request,
mark_safe("Successfully created
instance in other model: <a
href=\"{}\">new
instance</a>".format(url))
)
|
{% url
'admin:myapp_mymodel_change'
mymodel.id
%} |
(custom) |
{{ app_label }}_{{ model_name }}_myview |
|
|
myview must be registered at
MyModelAdmin.get_urls()
- {% url 'admin:myapp_mymodel_myview'
%}
- {% url opts|admin_urlname:"myview"
%}
|
- View
-
|
|
|
my_project/my_app/
|
my_project/templates/
|
URL style
|
HTTP
method
|
|
models.py
|
forms.py
|
urls.py |
views.py
|
my_app/*.html
|
base.html
|
{prefix}/ |
GET
|
list
|
from
django.db
import models
class MyModel(models.Model):
name =
... |
|
from my_app.views import MyModelListView
url(r'^mymodels/$', MyModelListView.as_view()),
from my_app.views import MyModelListView
url(r'^mymodels/$', MyModelListView.as_view( template_name
=
'your_template.html' )),
|
from django.views
import generic class
MyModelListView(generic.ListView):
# equvalent to
queryset=MyModel.objects.all()
model = MyModel
# optional:
# default:
'mymodel_list'
context_object_name =
'your_name_for_list'
# default:
'mymodel_list.html'
template_name = 'your_template.html'
|
- mymodel_list.html (or specified by
template_name)
- object_list, mymodel_list (or specified by
context_object_name)
{% extends "base.html" %}
{% block content %}
<h2>MyModels</h2>
<ul>
{% for mymodel in mymodel_list %}
<li>{{
mymodel.name }}</li>
{% endfor %}
</ul>
{% endblock %}
|
{% load staticfiles %}
<html>
<head>
<title>{% block title
%}{% endblock %}</title>
</head>
<body>
<img src="{% static
"images/sitelogo.png" %}" alt="Logo" />
{% block content %}{% endblock
%}
</body>
</html>
|
{prefix}/{something} |
class Publisher(models.Model):
name =models.CharField(max_length=30)
class Book(models.Model):
title = models.CharField(max_length=100)
publisher = models.ForeignKey(Publisher)
|
|
from my_app.views import MyModelListView
url(r'^books/([\w-]+)$',
MyModelListView.as_view()),
|
class
PublisherBookList(ListView):
template_name = 'books/books_by_publisher.html'
def get_queryset(self):
self.publisher
= get_object_or_404(Publisher,name=self.args[0])
return Book.objects.filter(publisher=self.publisher)
|
{% extends
"base.html" %}
{% block content %}
<h2>Books</h2>
<ul>
{% for book in book_list
%}
<li>{{
book.title }}</li>
{% endfor %}
</ul>
{% endblock %} |
...
# optional: add entry to the context
def get_context_data(self, **kwargs):
# Call the base implementation
first to get a context
context =
super(PublisherBookList,
self).get_context_data(**kwargs)
# Add in the publisher
context['publicador']
=
self.publisher
return context |
{% extends
"base.html" %}
{% block content %}
<h2>Books for publisher {{publicador.name}}</h2>
<ul>
{% for book in book_list
%}
<li>{{
book.title }}</li>
{% endfor %}
</ul>
{% endblock %} |
|
POST
|
create
|
|
|
url(r'^$', views.MyModelCreateView.as_view(),
name='mymodel_create'), |
from django.views
import generic class
MyModelCreateView(generic.CreateView):
model = MyModel |
- <model_name>_form.html (or specified by
template_name)
|
{prefix}/{lookup}/ |
GET
|
retrieve
|
|
|
|
from
django.views import
generic
class MyModelDetailView(generic.DetailView):
model = MyModel |
|
|
PUT
|
update
|
|
|
|
|
|
|
PATCH
|
partial_update |
|
|
|
|
|
|
DELETE
|
destroy
|
|
|
|
|
|
- Polls
example views
- Function-based views
- request
- return response
type
|
return ...
|
import
|
|
"toto"
|
|
|
HttpResponse("value:
%s"
% toto) |
from
django.http import HttpResponse |
text/plain
|
HttpResponse("toto
text",
content_type="text/plain") |
application/json
|
HttpResponse(json.dumps({'key':'value'}),
content_type="application/json") |
from
django.http import HttpResponse
import json
|
text/html
|
tplt =
loader.get_template( 'polls/detail.html' )
ctxt = Context(
{'poll_var': p} )
HttpResponse(tplt.render(ctxt)) |
from
django.http import HttpResponse |
#
deprecated (use render() instead)
render_to_response ('polls/detail.html',
{'poll_var':
p}) |
from
django.shortcuts
import render_to_response |
#
deprecated (use render() instead)
render_to_response('polls/detail.html',
{'poll_var':
p},
context_instance=RequestContext(request))
|
render(request,
' polls/detail.html', {'poll_var':
p}) |
from django.shortcuts
import render |
TemplateResponse,
SimpleTemplateResponse
|
|
application/json
(or default renderer)
|
Response(serializer.data,
status=status.HTTP_ ...)
|
djangorestframework
|
text/html |
render(
Response(...) )
|
application/ms-excel
|
|
xlwt
|
- Class-based
views (API: Class-based
views) (see also: REST
routers)
- Trailing slash
- Redirect
- Problemes / Problems
- This QueryDict instance is immutable when doing a POST with
application/x-www-form-urlencoded (not a problem with
multipart nor json)
- REST views:
|
View
|
|
Views
|
REST
views
|
REST
API |
framework
|
-
|
Django
REST framework
|
Tastypie
|
file
|
views.py |
views.py |
api.py |
type
|
ORM |
non-ORM |
ORM |
non-ORM |
ORM
|
non-ORM |
import |
|
|
|
from
djangorestframework.views import View
|
from
tastypie.resources import ModelResource
|
from
tastypie.resources import Resource |
from
polls.models import Poll |
|
|
from
resourceexample.forms import MyForm |
from
llibres.models import Llibre |
|
body
|
|
|
|
class
AnotherExampleView(View):
form = MyForm
def post(self, request)
return "POST
request with content: %s" % (repr(self.CONTENT)) |
class
LlibreResource(ModelResource):
class Meta:
queryset = Llibre.objects.all()
resource_name = 'recurs_llibre' |
class
LlibreResource(Resource):
uuid = fields.CharField(attribute='uuid')
class Meta:
object_class =
Llibre.objects.all()
resource_name = 'recurs_llibre'
def detail_uri_kwargs(...)
def get_object_list(...)
def obj_get_list(...)
def obj_get(...)
def obj_create(...)
def obj_update(...)
def obj_delete_list(...)
def obj_delete(...)
def rollback(...)
class TotoObject(object):
def __init__(...)
|
|
Exemple Polls / Polls example
|
- djcode/mysite
- manage.py
- urls.py (URLconf)
from django.conf.urls.defaults
import patterns, include, url
# admin
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^polls/$',
'polls.views.index'),
url(r'^polls/(?P<poll_id>\d+)/$',
'polls.views.detail'),
url(r'^polls/(?P<poll_id>\d+)/results/$',
'polls.views.results'),
url(r'^polls/(?P<poll_id>\d+)/vote/$',
'polls.views.vote'),
url(r'^admin/',
include(admin.site.urls)),
)
from django.conf.urls.defaults
import patterns, include, url
# admin
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('polls.views',
url(r'^polls/$', 'index'),
url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
url(r'^polls/(?P<poll_id>\d+)/results/$',
'results'),
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
)
urlpatterns += patterns('',
url(r'^admin/',
include(admin.site.urls)),
)
# This also imports the
include function
from django.conf.urls.defaults import *
# admin
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^polls/', include('polls.urls')),
url(r'^admin/',
include(admin.site.urls)),
)
- settings.py
- polls.db (base de dades / database) (si va amb Apache, ha de pertànyer
al grup www-data)
- polls
- test.py
- Models
- models.py
(model de la base de dades)
- admin.py (gestió de la base de dades)
fields : ordre dels camps / field
sorting
admin.site.register : fa que surti
a la pàgina principal / make it appears at the
main page
fieldsets : agrupacions de camps /
groups of fields
inlines : fa que surti a dins
(relacionat via foreign key), en lloc del
principi de tot / embedded instead of at the
main page
admin.StackedInline : llista /
list
admin.TabularInline : taula,
més compacta que la llista / table, more
compact than list
list_display :
selecció de camps que cal presentar a "Select
poll to change" (si no, només mostrarà els
especificats per __unicode__ )
- filtres / filters:
- cerques / searches:
- navegació per dates / navigation by date:
- date_hierarchy
- dependències / dependencies
from polls.models
import Poll
from django.contrib import admin
admin.site.register(Poll)
from polls.models
import Poll
from django.contrib import admin
class PollAdmin(admin.ModelAdmin):
model = Poll
admin.site.register(Poll, PollAdmin)
-
from polls.models
import Poll
from django.contrib import admin
class PollAdmin(admin.ModelAdmin):
fields = ['pub_date',
'question']
admin.site.register(Poll, PollAdmin)
-
from polls.models
import Poll
from django.contrib import admin
class PollAdmin(admin.ModelAdmin):
fieldsets
= [
(None, {'fields': ['question']}),
('Date information', {'fields': ['pub_date'],
'classes': ['collapse']}),
]
admin.site.register(Poll, PollAdmin)
from polls.models
import Poll
from polls.models import Choice
from django.contrib import admin
class ChoiceInline(admin.StackedInline):
model = Choice
extra = 3
class PollAdmin(admin.ModelAdmin):
fieldsets = [
(None,
{'fields':
['question'] , 'classes': ['wide'] }),
('Date information', {'fields': ['pub_date'],
'classes': ['collapse']}),
]
inlines = [ChoiceInline]
list_display = ('question',
'pub_date', 'was_published_today')
list_filter = ['pub_date']
search_fields =
['question']
date_hierarchy = 'pub_date'
admin.site.register(Poll, PollAdmin)
- Views
- urls.py
(quina
"view" cal presentar quan es rep una petició amb
aquell patró d'url) (aquest és el contingut del
fitxer quan el fitxer urls.py del pare inclou
include('polls.urls') )
- opcionalment se li pot donar un nom (
,
name='etiqueta' ), per a referir-s'hi
després des de views.py, amb, per exemple: [reverse('etiqueta',
kwargs={'num':num})
for num in range(3)]
- es fan servir expressions
regulars / regular expressions are used
from
django.conf.urls.defaults import patterns,
include, url
urlpatterns = patterns('polls.views',
url(r'^$',
'index'),
url(r'^(?P<poll_id>\d+)/$',
'detail'),
url(r'^(?P<poll_id>\d+)/results/$',
'results'),
url(r'^(?P<poll_id>\d+)/vote/$',
'vote'),
)
- views.py
(com es presenten els resultats)
- Text simple (text/plain):
from django.http
import HttpResponse
def index(request):
return
HttpResponse("Hello, world. You're at the
poll index.")
def detail(request, poll_id):
return
HttpResponse("You're looking at poll %s."
% poll_id)
def results(request, poll_id):
return
HttpResponse("You're looking at the
results of poll %s." % poll_id)
def vote(request, poll_id):
return
HttpResponse("You're voting on poll %s." %
poll_id)
- Text simple (text/plain) a partir de base de
dades:
from polls.models
import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list =
Poll.objects.all().order_by('-pub_date')[:5]
output = ',
'.join([p.question for p in
latest_poll_list])
return
HttpResponse(output)
from polls.models
import Poll,Choice
from django.http import HttpResponse
def detail(request, poll_id):
p = Poll.objects.get(pk=poll_id)
output =
m.choice_set.all()
return
HttpResponse(output)
- Text formatat (text/html) a partir de la base
de dades, segons una plantilla
HTML
from
django.template import Context, loader
from polls.models import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list =
Poll.objects.all().order_by('-pub_date')[:5]
t =
loader.get_template('polls/index.html')
c = Context({
'latest_poll_list': latest_poll_list,
})
return
HttpResponse(t.render(c))
from
django.shortcuts import render_to_response
from polls.models import Poll
def index(request):
latest_poll_list =
Poll.objects.all().order_by('-pub_date')[:5]
return
render_to_response('polls/index.html',
{'latest_poll_list': latest_poll_list})
...
from django.http import Http404
def detail(request, poll_id):
try:
p = Poll.objects.get(pk=poll_id)
except
Poll.DoesNotExist:
raise Http404
return
render_to_response('polls/detail.html',
{'poll': p})
...
from django.shortcuts import
render_to_response, get_object_or_404
from django.template import RequestContext
def detail(request,
poll_id):
p
= get_object_or_404(Poll, pk=poll_id)
return
render_to_response('polls/detail.html',
{'poll_var':
p},
context_instance=RequestContext(request))
...
from django.http import
HttpResponseRedirect
from django.core.urlresolvers import
reverse
def vote(request,
poll_id):
p =
get_object_or_404(Poll, pk=poll_id)
try:
selected_choice =
p.choice_set.get(pk=request.POST['choice'])
except (KeyError,
Choice.DoesNotExist):
# Redisplay the poll voting form.
return
render_to_response('polls/detail.html', {
'poll':
p,
'error_message':
"You didn't select a choice.",
},
context_instance=RequestContext(request))
else:
selected_choice.votes += 1
selected_choice.save()
# Always return an HttpResponseRedirect
after successfully dealing
# with POST data. This prevents data from
being posted twice if a
# user hits the Back button.
return
HttpResponseRedirect(reverse('polls.views.results',
args=(p.id,)))
def results(request,
poll_id):
p =
get_object_or_404(Poll, pk=poll_id)
return
render_to_response('polls/results.html',
{'poll': p})
- Error views (404, 500, ...)
- variables in URLconf (urls.py):
handler404, handler500
- files: 404.html, 500.html
- templates (Template
guide) (plantilles
html per a presentar els resultats / templates for
showing the results) )
- index.html
{% if latest_poll_list
%}
<ul>
{% for poll in
latest_poll_list %}
<li><a href="/polls/{{ poll.id
}}/">{{ poll.question }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are
available.</p>
{% endif %}
- detail.html
-
<h1>{{ poll_var.question }}</h1> <ul> {% for choice in poll_var.choice_set.all %} <li>{{ choice.choice }}</li> {% endfor %} </ul>
-
<h1>{{ poll.question }}</h1> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <form action="/polls/{{ poll.id }}/vote/" method="post"> {% csrf_token %} {% for choice in poll.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br /> {% endfor %} <input type="submit" value="Vote" /> </form>
- results.html
|
Fitxers estàtics / Static files
|
- Documentació / Documentation
- Static files (css, js, ...) (different from media files) (Note)
(es pot obtenir des de: / can be obtained from: HTML templates)
- general config
mysite/mysite/settings.py
# settings
for
django.contrib.staticfiles
# only for production, destination of files
collected by collectstatic
script:
STATIC_ROOT = ' /home/user/src/ djcode/mysite/collected_static/
'
STATIC_ROOT
=
os.path.join(BASE_DIR, 'collected_static')
# used for creation of urls pointing to static
content:
STATIC_URL
= '/static/'
# in addition to static files from apps (inside
myapp/static),
# collect the static files from:
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static_general"),
)
INSTALLED_APPS = (
...
'django.contrib.staticfiles',
)
- put the static files in their place:
- general (per site) static files:
- put them on the directory specified by
STATICFILES_DIR:
/home/user/src/ djcode_1.4/mysite/mysite/static_general/
- app-specific static files:
- 1.6
mysite/polls/static/polls/css/style.css
- old versions: put them on the "static"
dir inside the app:
mysite/polls/static/css/polls.css
- Only for production / integration with Apache or Nginx:
- copy them to
STATIC_ROOT (in flat
structure), specified in settings.py , by
calling the script:
python manage.py collectstatic
- it will take all the static content in every app
(doesn't need to be specified on STATICFILES_DIRS)
and all other general static files under
STATICFILES_DIRS
- add the static directory (
STATIC_ROOT )
(no need to be under djcode_1.4 ) to Apache config file (Alias
must match the STATIC_URL definition):
IMPORTANT:
comproveu els permisos
d'usuari UNIX del directori / check the UNIX user
permissions of the directory
Alias /static/
/home/user/src/djcode_1.4/mysite/ collected_ static/
<Directory /home/user/src/djcode_1.4/mysite/ collected_ static/>
# Apache 1.2:
#Order deny,allow
#Allow from all
# Apache 1.4:
Require all granted
</Directory>
- reference
to
static file
- mysite/mysite/settings.py
TEMPLATE_CONTEXT_PROCESSORS
= (
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
)
- mysite/myapp/views.py (only needed for function-based
views?)
from django.template
import RequestContext
... return
render_to_response('toto.html', context_instance=RequestContext(request))
- from templates
- 1.6: mysite/templates/myapp/toto.html
{% load staticfiles %}
<link rel="stylesheet" type="text/css"
href="{% static 'polls/css/style.css' %}"
/>
- old versions: mysite/myapp/templates/toto.html
<img src="{{
STATIC_URL }}images/hi.jpg"
alt="Hi!" />
- from other css
- 1.6: mysite/polls/static/polls/css/style.css
body {
# image is in
polls/static/polls/images/background.gif
background: white
url("images/background.gif") no-repeat right
bottom;
}
|
Media
|
- Imatges / Images
- Vídeo / Video
- Managing
files
- Resum / Summary
type
|
storage
|
destination |
|
access
|
|
|
|
root |
path |
<MEDIA_URL>/... |
local |
|
|
<MEDIA_ROOT>/<upload_to>/<filename> |
|
|
|
|
- default (settings.py):
- non-default (models.py):
my_field = models.FileField( ... storage=OverwriteStorage()
...)
|
- default (settings.py):
- non-default (.py):
|
- static (models.py):
media = models.ImageField(upload_to='im')
- runtime (models.py):
def media_filename(instance, filename):
...
media = models.ImageField(upload_to=media_filename)
|
|
remote |
AWS S3 |
|
s3://<AWS_STORAGE_BUCKET_NAME>/<location>/<upload_to>/<filename> |
|
|
|
|
- default:
- settings.py
DEFAULT_FILE_STORAGE =
'storages.backends.s3boto.S3Boto3Storage'
DEFAULT_FILE_STORAGE =
'myapp.s3utils.MyFirstBucketS3Storage'
- models.py
class MyModel(models.Model):
# uses default storage,
specified by DEFAULT_FILE_STORAGE
my_field =
models.FileField()
- non-default:
- models.py
from s3utils import
MyFirstS3Storage, MySecondS3Storage
class MyModel(models.Model):
my_field =
models.FileField(storage=MyFirstS3Storage(),
upload_to=...)
|
- default (settings.py):
- bucket:
AWS_STORAGE_BUCKET_NAME = 'mybucket'
- root path:
AWS_LOCATION = "mylocation"
DEFAULT_S3_PATH = "media"
MEDIA_ROOT = '/%s/' % DEFAULT_S3_PATH
- non-default:
- bucket:
- s3utils.py
@deconstructible
class
MyFirstBucketS3Storage(S3Boto3Storage):
bucket_name =
'myfirstbucket'
@deconstructible
class
MyFirstBucketS3Storage(S3Boto3Storage):
def __init__(self,
*args, **kwargs):
kwargs['bucket'] =
'myfirstbucket'
kwargs['file_overwrite'] = False
super(MyFirstBucketS3Storage,
self).__init__(*args, **kwargs)
- location
- s3utils.py
- @deconstructible
class
NewMediaS3BotoStorage(S3Boto3Storage):
location='mylocation'
|
(same as above) |
- my_field.url
- default (settings.py):
DEFAULT_S3_PATH = "media"
CLOUDFRONT_DOMAIN =
'domain.cloudfront.net'
MEDIA_URL = 'http://%s/%s/' %
(CLOUDFRONT_DOMAIN, DEFAULT_S3_PATH)
- non default:
- s3utils.py
@deconstructible
class
MyFirstBucketS3Storage(S3Boto3Storage):
# to be used with
CloudFront
custom_domain =
'myfirstbucket.mydomain.com'
@deconstructible
class
MyFirstBucketS3Storage(S3Boto3Storage):
def __init__(self,
*args, **kwargs):
kwargs['custom_domain'] =
'myfirstbucket.mydomain.com'
super(MyFirstBucketS3Storage,
self).__init__(*args, **kwargs)
|
- settings.py
# settings
for
file uploads
# absolute dir place to upload files (will be completed
with directory specified by "upload_to")
# only needed in production
# parent dir (belonging to my_username )
must have the right permissions:
# sudo usermod -a -G www-data my_username
# sudo chgrp www-data -R <MEDIA_ROOT>
# sudo chmod -R g+w <MEDIA_ROOT>
# sudo chmod -R g+s <MEDIA_ROOT>
MEDIA_ROOT
=
PROJECT_PATH + '/media/'
MEDIA_ROOT
= os.path.join(BASE_DIR, 'media')
#
used for creation of urls pointing to
media content: MEDIA_URL
= '/mm/'
#MEDIA_URL = 'http://other_server/mm/'
- Puja fitxers / Upload files
- File
Uploads
- Pujar un fitxer des d'un botó
a l'admin
- Progrés / Progress
- a un directori amb un nom ('im') fix / into a fixed name
('im') directory (uploaded files will be stored in
MEDIA_ROOT/im/ ):
class Imatge(models.Model):
nom =
models.CharField(max_length=100)
media = models.ImageField(upload_to='im')
- a un directori amb un directori i un nom creats
dinàmicament / into a dynamically created directory and name
(e.g. directory based on 'im' and user name; and file name
created as uuid):
import os
def media_filename(instance,
filename):
ext = filename.split('.')[-1]
new_filename = "%s.%s" %
(uuid.uuid4(), ext)
return os.path.join('im',
instance.user.username, new_filename)
class Imatge(models.Model):
nom =
models.CharField(max_length=100)
user = models.ForeignKey(
UploadUser )
media = models.ImageField(upload_to=media_filename)
- Problemes / Problems
- Serving
files
uploaded by a user
- Desenvolupament / Development
- urls.py
from django.conf import
settings
from django.conf.urls.static import static
urlpatterns = patterns('',
...
)
if settings.DEBUG:
urlpatterns +=
static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
- Producció /
Production
- Sobreescriu els fitxers, vídeo
o imatges pujats /
Overwrite upload files, video
or images
- Bug 4339
/ 11663
(comment
#19)
- models.py
from
django.core.files.storage import FileSystemStorage
class OverwriteStorage(FileSystemStorage):
def _save(self, name, content):
if
self.exists(name):
self.delete(name)
return
super(OverwriteStorage, self)._save(name, content)
# to avoid adding underscores
for existing files
def get_available_name(self,
name):
return
name
# to avoid replacement of
spaces with underscores
def get_valid_name(self, name):
return
name
class Toto(models.Model)
toto_file = models.FileField(upload_to="toto_dir",
storage=OverwriteStorage(),
blank=True)
- Overwrite
File Storage System
- ImageField overwrite image file
- ...
|
Vídeo / Video
|
|
Imatges / Images
|
- Projectes relacionats / Related projects:
- django-photologue
- django-imagekit
- django-imaging
(Ajax driven gallery field for django admin): not working
for ManyToMany
- django-stdimage
- Features
- Rotate from EXIF
- Rotate
variations or keep exif info for JPEG #66
from stdimage.utils
import render_variations
from django.core.files.base import ContentFile
from PIL import Image
from io import BytesIO
def resize_and_autorotate(file_name,
variations, storage):
#
https://github.com/codingjoe/django-stdimage/issues/66
with
storage.open(file_name) as f:
with Image.open(f) as image:
file_format = image.format
if hasattr(image, '_getexif'): # only present
in JPEGs
exif = image._getexif()
#
if image has exif data about orientation,
let's rotate it
orientation_key = 274 # cf ExifTags
if exif and orientation_key in exif:
orientation
= exif[orientation_key]
print
"[process_image] EXIF orientation:
%s" % (orientation)
rotate_values
= {
3:
Image.ROTATE_180,
6:
Image.ROTATE_270,
8:
Image.ROTATE_90
}
if
orientation in rotate_values:
image
= image.transpose(rotate_values[orientation])
with BytesIO() as file_buffer:
image.save(file_buffer,
file_format)
f
= ContentFile(file_buffer.getvalue())
storage.delete(file_name)
storage.save(file_name,
f)
# render stdimage
variations
render_variations(file_name, variations,
replace=True, storage=storage)
return False #
prevent default rendering
- Problemes / Problems
- Problemes / Problems
manage.py rendervariations ...
- si no s'especifica storage dins de
StdImageField, quan agafa el valor per defecte
especificat a / if not specified inside
StdImageField, when it takes the default value
specified in settings.py
DEFAULT_FILE_STORAGE
= 'my_app.s3utils.NewMediaS3BotoStorage' :
ImportError: Module
"django.core.files.storage" does not
define a "NewMediaS3BotoStorage"
attribute/class
- Solució / Solution
myfield = StdImageField( ..., storage=...
)
- no fa cas de bucket ni location especificats
dins de / it does not take into account bucket
and location specified in
storage=NewMediaS3BotoStorage(bucket=...,
location=...)
File does not exist
- Solució / Solution
- el bucket s'ha d'especificar a /
bucket must be specified in
settings.AWS_STORAGE_BUCKET_NAME
- el location s'ha d'especificar a /
location must be specified in
my_app/s3utils.py:
@deconstructible
class
NewMediaS3BotoStorage(S3BotoStorage):
location=settings.S3_MEDIA_PATH
- Example with AWS S3 (storages),
Celery, djangorestframework
- myproject/settings.py
INSTALLED_APPS = (
...
'stdimage',
...
)
AWS_STORAGE_BUCKET_NAME
= 'my-bucket'
AWS_AUTO_CREATE_BUCKET = True
# media (uploaded files)
S3_MEDIA_PATH = "media"
MEDIA_ROOT = '/%s/' % S3_MEDIA_PATH
- my_app/s3utils.py
from
storages.backends.s3boto import S3BotoStorage
from django.conf import settings
from django.utils.deconstruct import
deconstructible
@deconstructible
class NewMediaS3BotoStorage(S3BotoStorage):
"""
Deconstructible subclass to
avoid Django 1.7 'manage.py makemigrations'
error:
ValueError: Cannot
serialize:
<storages.backends.s3boto.S3BotoStorage
object...
"""
location=settings.S3_MEDIA_PATH
- my_app/tasks.py
from celery import
shared_task
from stdimage.utils import render_variations
@shared_task
def process_image(file_name,
variations, storage):
print "[process_image]
file_name=%s, variations=%s, storage=%s" %
(file_name, variations, storage)
# optionally: rotate from EXIF
render_variations(file_name, variations,
replace=True, storage=storage)
- my_app/models.py
from my_app.tasks
import process_image
from stdimage.models import StdImageField
def image_processor(file_name,
variations, storage):
process_image.delay(file_name,
variations, storage)
return False #
prevent default rendering
class MyModel(models.Model)
cover =
StdImageField(_("Cover"), help_text=_("Cover
image"),
storage=NewMediaS3BotoStorage(),
upload_to='covers',
variations={'large':
(600, 400),
'thumbnail':
(100, 100, True),},
render_variations=image_processor,
blank=True,
null=True)
- my_app/serializers.py
from django.forms
import ImageField as DjangoImageField
from rest_framework import serializers
class VariationImageField(serializers.ImageField):
"""
Serializer for
StdImageField, with specified variation.
"""
def __init__(self, *args,
**kwargs):
self._DjangoImageField =
kwargs.pop('_DjangoImageField',
DjangoImageField)
self.variation
= kwargs.pop('variation','')
super(VariationImageField,
self).__init__(*args, **kwargs)
def to_internal_value(self,
data):
#
override to deal with "null" data
if
data=='null':
# NOTE: use allow_null=True instead
# TODO: if the file existed, really delete it
from storage
return None
else:
file_object
= super(fields.ImageField,
self).to_internal_value(data)
django_field = self._DjangoImageField()
django_field.error_messages =
self.error_messages
django_field.to_python(file_object)
return file_object
def to_representation(self,
value):
use_url = True
if
not value:
return None
if
use_url:
if not getattr(value, 'url', None):
#
If the file has not been saved it may not have
a URL.
return None
# if exists, take the variation
image = getattr(value, self.variation, value)
url = image.url
request = self.context.get('request', None)
if request is not None:
return
request.build_absolute_uri(url)
return url
return value.name
class
MyModelSerializer(serializers.ModelSerializer):
cover = VariationImageField(
variation='thumbnail'
)
- sorl-thumbnail
- Requisits / Requirements
- Utilització / Usage
- models.py
class
UploadImatge(models.Model)
media = models.ImageField(upload_to='ims')
- camins / paths
- relative (does not include MEDIA_ROOT):
- absolute (includes MEDIA_ROOT, if used storage is
FileSystemStorage or derived; when using S3BotoStorage,
it will be the same as imatge.media.name):
- Visualitza imatges / Display images:
- AdminImageWidget
- usage
- simplified version
- ManyToMany
- ManyToMany
- Option 1: define your own image_thumb field and use it
with ForeignKey (admin of Image: works; inline admin of
Mosaic: it works)(sí que es veu la imatge en petit a
l'inline; però una ImatgeDeFilm està lligada a un sol film i
no es pot reutilitzar) en lloc de ManyToMany (no es veu la
imatge a l'inline, perquè és un seleccionable simple):
- models.py
class Film(models.Model):
titol =
models.CharField(max_length=100)
class ImatgeDeFilm(models.Model):
nom =
models.CharField(max_length=100)
media = models.ImageField(upload_to='ims')
film = models.ForeignKey(Film)
def image_thumb(self):
return
u'<img src="%s" width="100" height="100"/>'
% self.media.url
image_thumb.short_description =
'Imatge en petitet'
image_thumb.allow_tags = True
def __unicode__(self):
return
"%s (%s)" % (self.nom, self.media)
- admin.py
class
ImatgeDeFilmInline(admin.TabularInline):
model = ImatgeDeFilm
readonly_fields = ['image_thumb']
class FilmAdmin(admin.ModelAdmin):
inlines
= [ImatgeDeFilmInline]
admin.site.register(Film, FilmAdmin)
- Option 2: define your own image_thumb field and use it
with ManyToMany (admin of Image: it works; inline admin of
Mosaic: it does not work)
- models.py
class
Imatge(models.Model):
nom =
models.CharField(max_length=100)
media = models. ImageField (upload_to='ims')
def __unicode__(self):
return
"%s (%s)" % (self.nom, self.media)
# to allow display of images in
admin
def image_thumb(self):
return
u'<img src="%s" width="100" height="100"/>'
% self.media.url
image_thumb.short_description =
'Imatge en petitet'
image_thumb.allow_tags = True
class
Mosaic(models.Model):
titol =
models.CharField(max_length=100)
imatges = models.ManyToManyField(Imatge,
related_name='mosaics')
- admin.py
class
ImatgeAdmin(admin.ModelAdmin):
# to display image thumbnail in
the list:
list_display = ('nom','image_thumb',)
# to display image thumbnail on
adding an image (inline or not)
readonly_fields = ['image_thumb']
admin.site.register(Imatge, ImatgeAdmin)
- not inline in mosaic (simple select multiple):
class
MosaicAdmin(admin.ModelAdmin):
fields = ('titol',
'imatges')
admin.site.register(Mosaic, MosaicAdmin)
- not inline in mosaic (better select multiple in
two horizontal columns: "available", "selected")
class
MosaicAdmin(admin.ModelAdmin):
fields =
('titol','imatges')
filter_horizontal =
['imatges',]
admin.site.register(Mosaic, MosaicAdmin)
- inline images in mosaic (Working
with
many-to-many models):
class
MosaicImatgeInline(admin.TabularInline):
model =
Mosaic.imatges.through
class MosaicAdmin(admin.ModelAdmin):
exclude = ('titol', )
inlines =
[MosaicImatgeInline,]
admin.site.register(Mosaic, MosaicAdmin)
- Option 3: use AdminImageWidget
and ForeignKey
- Option 4: use AdminImageWidget
and ManyToMany (Image and video previews in
admin inline forms for ManyToMany relation)
- Generació d'imatges / Image generation:
|
Generació al vol de camps / On the
fly field generation
|
- models
- do something before the object is saved:
class Toto(models.Model):
...
def save(self, *args, **kwargs):
# do something only
when the entry is created
if not
self.pk:
...
# do
something every time the entry is updated
...
# then call the
parent save:
super(Toto,
self).save(*args, **kwargs)
- do something when the
manytomany relation changes:
from django.db.models.signals import m2m_changed
class Toto(models.Model):
toto_fills =
models.ManyToManyField(TotoFill)
def do_something(self):
...
def totofills_changed(sender, instance, **kwargs):
instance.do_something()
instance.save()
m2m_changed.connect(totbills_changed,
sender=Toto.toto_fills.through)
- How to know, before saving, if a field has changed
- django
-
comparing old and new field value before saving
- FieldTracker
(django-model-utils)
- models.py
from model_utils
import FieldTracker
class MyModel():
field1 = ...
...
# to track changes in
fields
tracker = FieldTracker()
def save(self, *args,
**kwargs):
if
self.tracker.has_changed('field1'):
#
old_value = self.tracker.previous('field1')
#
new_value = self.field1
...
- Create other object
when an object is saved (using signals)
- django how do i send a
post_save signal when updating a user?
- User profiles
- myfirstapp/models.py
from
django.db.models.signals import post_save
from django.dispatch import receiver
from django.db import transaction
class MyFirstModel(models.Model):
...
@receiver(post_save, sender= MyFirstModel )
def create_mysecondmodel_object(sender, instance,
**kwargs):
# create MySecondModel
from mysecondapp.models
import MySecondModel
try:
with
transaction.atomic():
my_second_object
= MySecondModel.objects.get_or_create(myfield=instance)
except Exception as e:
print e
- mysecondapp/models.py
from myfirstapp.models
import MyFirstModel
class MySecondModel(models.Model):
myfield =
models.OneToOneField(MyFirstModel)
- views
- admin
- automatically create a list_display
field:
def binary_status(obj):
return '{:08b}'.format(obj.status)
binary_status.short_description = _("Binary status")
class TotoAdmin(admin.ModelAdmin):
...
list_display = (...,
binary_status,...)
...
admin.site.register(Toto, TotoAdmin)
- URL in list_display:
class
TotoAdmin(admin.ModelAdmin):
list_display =
(...,'totofield_url', ...)
# hyperlink to totofield in
display_list
def totofield_url(self,item):
target =
getattr(item, 'totofield')
return
'<a href="../%s/%d/">%s</a>' %
(target._meta.module_name, target.id, unicode(target))
totofield_url.allow_tags = True
totofield_url.short_description =
_("Toto field")
admin.site.register(Toto, TotoAdmin)
- image thumbnail in list_display:
- my_project/settings.py
- my_app/models.py
class
Gallery(models.Model):
name =
models.CharField(max_length=256)
image =
models.ImageField(upload_to='im')
- my_app/admin.py
@admin.register(Gallery)
class GalleryAdmin(admin.ModelAdmin):
list_display =
('name','image_thumbnail',)
def image_thumbnail(self,
item):
return
u'<img src="%s" height="50"/>' %
(item.image.url)
image_thumbnail.short_description = 'Thumbnail'
image_thumbnail.allow_tags =
True
- servidor de
mèdia / media server
- fields from related fields
- admin.py
class
MyModelAdmin(admin.ModelAdmin):
fields = ('other_field',)
readonly_fields =
('other_field',)
def other_field(self, obj):
return
obj.related_field.other_field
- custom fields that are not part of the model:
- django admin - add custom
form fields that are not part of the model
- admin auto populate user
field from request
- django prepopulate
modelform - nothing happens
- example:
- models.py
class
MyModel(models.Model):
first_byte =
models.IntegerField()
second_byte =
models.IntegerField()
- admin.py
from django import
forms
class MyModelForm(forms.ModelForm):
WORD_16BIT_CHOICES = (
(0x0000,
"0x00-0x00: code for first option"),
(0x0001,
"0x00-0x01 : code for second
option "),
(0x0102,
"0x01-0x02
: code for
third option "),
(0x0201,
"0x02-0x01
:
code for fourth option
"),
(0xffff,
"0xff-0xff : code for last option "),
)
# new field (int from choices) not present in
the model
word_16bit =
forms.TypedChoiceField(label=_("16-bit word"),
choices=WORD_16BIT_CHOICES, required=False,
coerce=int)
def __init__(self, *args,
**kwargs):
super(MyModelForm, self).__init__(*args,
**kwargs)
#
if an instance already exists, get the initial
value from it
#
python 2: if kwargs.has_key('instance'):
if
"instance" in kwargs:
self.initial['word_16bit']
= self.instance.first_byte*256 +
self.instance.second_byte
def save(self,
commit=True):
#
calculate both bytes from the 16-bit word
word_16bit =
self.cleaned_data.get('word_16bit', 0)
self.instance.first_byte = word_16bit // 256
self.instance.second_byte = word_16bit % 256
return super(MyModelForm,
self).save(commit=commit)
class Meta:
model = MyModel
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
model = MyModel
form = MyModelForm
fields = ('word_16bit',)
- force save of unchanged inlines
- do something when ForeginKey related fields change:
- Get access to ForeignKey
objects at parent save in Django
- ModelAdmin.save_related
- models.py
class Toto(models.Model):
...
def do_something(self):
...
- admin.py
class
TotoAdmin(admin.ModelAdmin):
...
def save_related(self, request,
form, formsets, change):
# save
all related data (inlines)
super(TotoAdmin, self).save_related(request,
form, formsets, change)
# do
something
form.instance.do_something()
# if
something of parent model got modified in
do_something,
# save
the parent model again
super(TotoAdmin, self).save_model(request,
form.instance, form, change)
- Exemple: imatge composta
creada automàticament quan es desen les imatges
relacionades per ForeignKey (vegeu també conversió d'imatge amb
Celery) / Example: automatically created composed
image from ForeignKey related images (see also image conversion with
Celery):
- models.py
from django.conf
import settings
from PIL
import Image
class Toto(models.Model):
...
composed_image = models.ImageField(upload_to='images',
verbose_name=_("Composed
image"), help_text=_("Composed image"),
storage=OverwriteStorage(), blank=True)
def image_composition(self):
if
self.images.count() == 0:
return
NUMBER_OF_IMAGES=self.images.count()
#
recupera les mides de la primera de les
imatges /
#
get the size from the first image
image = self.images.all()[0]
OUT_WIDTH = image.media.width
OUT_HEIGHT = image.media.height
#
mida de la imatge que cal creat / size of the
image to create
size = (OUT_WIDTH*NUMBER_OF_IMAGES,OUT_HEIGHT)
# crea la imatge / create the image
out_image = Image.new('RGB', size)
total_name = "%d" % (self.pk)
left = 0
right = OUT_WIDTH
upper = 0
lower = OUT_HEIGHT
for
image in self.images.all():
print
image.media.file.name
in_image = Image.open(image.media.file.name)
position = (left, upper, right, lower)
print
" image: paste to (%d,%d,%d,%d)" %
(left, upper, right, lower)
out_image.paste(in_image,
position )
left = left + OUT_WIDTH
right
= right + OUT_WIDTH
image_filename = 'images/' + total_name +
'_composed.png'
absolute_image_filename = settings.MEDIA_ROOT
+ image_filename
log.info( "saving generated image %s" %
absolute_image_filename )
out_image.save(absolute_image_filename,'png')
#
actualitza el nom de la imatge composta /
#
update the name of the name containing the
composed image
self.composed_image.name = image_filename
class ImageOfToto(models.Model):
media = models.ImageField(upload_to='images',
verbose_name=_("Media"),
help_text=_("File or URL to be uploaded"),
storage=OverwriteStorage())
toto =
models.ForeignKey(Toto, related_name="images")
def image_thumb(self):
return u'<img src="%s" width="80"
height="44"/>' % self.media.url
image_thumb.short_description = _("Thumbnail")
image_thumb.allow_tags =
True
def __unicode__(self):
return u'%s' % (self.media)
- admin.py
class
TotoAdmin(admin.ModelAdmin):
# genera automàticament les
imatges compostes /
# automatically generate
the composed image
def save_related(self,
request, form, formsets, change):
#
desa les imatges relacionades / save related
images
super(TotoAdmin, self).save_related(request,
form, formsets, change)
#
genera la imatge composta / generate the
composed image
form.instance.image_composition()
#
desa el model pare per a desar els noms de la
imatge composta /
#
save the parent model to save the name of
composed image
super(TotoAdmin, self).save_model(request,
form.instance, form, change)
- serializers
|
|
- Estructura de
directoris
- Install
- Import an existing Django project into Eclipse (not
an Eclipse project)
- How
do
I import existing projects/sources for a Django project
into PyDev?
- File -> New -> Project
- PyDev ->
PyDev Django Project
- PyDev -> PyDev Project
- Project name: my_project
- Use default (/path/to/workspace/)
- disable "Use
default" / Browse: absolute_path_to_your_project_name
- Interpreter
- this will create .project and .pydevproject into
/path/to/workspace/my_project
- select your project
- PyDev -> Set as Django Project
- Properties -> PyDev-PYTHONPATH -> String
Substitution Variables -> Add variable
DJANGO_MANAGE_LOCATION: manage.py
(this will allow project to be run/debug as Django)
DJANGO_SETTINGS_MODULE: my_project.settings
- Debug / Run
- first time
- select your project
- Debug As -> PyDev: Django
- next times
- from top menu, select your entry from dropdown over
debug or run icon
- Test
- General method
- First time: select your project
- Django -> Run Django Tests
- Next time: you can select from menu (but this only
works if the first time run ok; if not, follow the
alternative method)
- Alternative method: copy debug configuration from main
debug
- select your project
- duplicate the Debug configuration to a new one and modify
it
- Alternative method: custom command
- select your project
- Django -> Custom command
- e.g.:
test --settings
my_project.test_settings --verbosity 3 ...
- modify
Debug configuration:
- Main
- Project:
myproject
- Main Module:
${workspace_loc:wct_streaming_farm}/${DJANGO_MANAGE_LOCATION}
- Arguments
- Program arguments:
test --settings
my_project.tests_settings --verbosity 3
...
- Working directory
- Default:
${project_loc:/selected
project name}
- ...
- Breakpoints
- debug response, even before reaching your code:
.../lib/python2.7/site-packages/django/core/handlers/wsgi.py :
line 168 (WSGIHandler.__call__: return response )
- when performing tests
.../lib/python2.7/site-packages/django/test/client.py:
ClientHandler.__call__: response =
self.get_response(request) (line 144)
- Errors
- Undefined variable from import
- Debug with shell
- PyDev console -> Python console
- import django
- django.setup()
- from django.conf import settings
- settings.configure()
- from my_app.models import *
- ...
|
|
- Databases
(1.8)
- Creació / Creation
-
|
first host (e.g. local server) |
second host (e.g. moving from local server to AWS Aurora) |
|
hostname=127.0.0.1
su postgres |
hostname=...rds.amazonaws.com |
create user (using master_user)
and give it permissions to create databases |
master_user=postgres
db_user=my_user
db_password=my_password
psql --host=${hostname} --username=${master_user}
--password --command="CREATE USER ${db_user} WITH
PASSWORD \'${db_password}\' CREATEDB;" |
create database (using db_user) |
db_name=my_db
createdb --host=${hostname} --password
--username=${db_user} ${db_name} |
- only needed if not using
pg_restore
--create
|
setup GIS (optional) in created database |
psql --host=${hostname} --password
--username=${db_user} --dbname=${db_name}
--command="CREATE EXTENSION postgis;" |
(not needed; it will be done by my_db_dump.sql) |
dump single database content |
dump_filename= my_db_dump.sql
pg_dump --dbname=${db_name}
--file=${dump_filename}
|
|
get content from single database dump |
|
# this does not accept --create nor
--format=directory
psql --host=${hostname} --password
--username=${master_user} --dbname=${db_name}
--file=${dump_filename}
- # not
working?:
pg_restore
--host=${hostname} --password
--username=${master_user} --create
--file=${dump_filename}
|
|
# for small databases
pg_dump --dbname=${db_name} | pgsql
--host=${hostname} --password
--username=${master_user} --dbname=${db_name} |
- Clear database
- Bolcat / Dump
- Configuració
de
la base de dades / Database configuration
-
|
|
mysite/settings.py |
Linux packages
|
pip install
|
|
|
'ENGINE'
|
CentOS
|
|
MariaDB
/ MySQL |
|
'django.db.backends.mysql' |
yum install
mariadb mariadb-server mariadb-devel
|
MySQL-python
|
PostgreSQL |
|
'django.db.backends.postgresql_psycopg2'
|
yum install
postgresql-server postgresql-devel
|
psycopg2
|
GIS
|
'django.contrib.gis.db.backends.postgis' |
|
|
SQLite |
|
'django.db.backends.sqlite3' |
|
|
- AWS
RDS
- Create a PostgreSQL database from AWS console:
- Master user:
my_master_user
- Database:
my_database
- PostgreSQL GIS example:
- setup_aws_rds.sh
#!/bin/bash
rds_host=xxxx.yyyyyy.eu-west-1.rds.amazonaws.com
master_user=my_master_user
master_database=postgres
database_name=my_database
# create user user_name=my_user
user_password=$(cat db_p.txt)
psql -h ${rds_host} --username=${master_user}
${master_database} --command="CREATE USER
$user_name WITH PASSWORD '${user_password}'
CREATEDB;"
# create postgis extension for my_database
psql -h ${rds_host} --username=${master_user}
${database_name} --command="CREATE EXTENSION
postgis;"
- settings.py
DATABASES = {
'default': {
'ENGINE':
'django.contrib.gis.db.backends.postgis',
'NAME':
' my_database',
'USER':
' my_user',
'PASSWORD': open(os.path.join(BASE_DIR,
'db_p.txt'),'r').read().strip(),
'HOST':' xxxx.yyyyyy.eu-west-1.rds.amazonaws.com',
'PORT':
'',
#
Set to empty string for default.
}
}
- Multiple databases
- Replicació /
Replication
- Example 1: default for read/write, readonly for read
- Scaling
Django with Postgres Read Replicas
- When
and How to Configure a Read Replica Database in Django
- my_project/settings.py
DATABASES = {
# e.g.: AWS Aurora Cluster
endpoint
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME':
'my_db_master',
'USER':
'my_master_user',
'PASSWORD': 'my_master_password',
'HOST':
'host_for_default',
'PORT':
'', # Set to empty string for default.
},
# e.g.: AWS Aurora Reader
endpoint
'replica_1': {
'ENGINE': 'django.db.backends.postgresql',
'NAME':
'my_db_replica_1',
'USER':
'my_replica_1_user',
'PASSWORD': 'my_replica_1_password',
'HOST':
'host_for_replica_1',
'PORT':
'', # Set to empty string for default.
},
# e.g.: AWS Aurora Reader endpoint
'replica_2': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'my_db_replica_2',
'USER': 'my_replica_2_user',
'PASSWORD': 'my_replica_2_password',
'HOST': 'host_for_replica_1',
'PORT': '', # Set to empty string for default.
} }
DATABASE_ROUTERS = ['my_project.routers.MyRouter']
- my_project/routers.py
import random
class MyRouter:
#route_app_labels = {}
def db_for_read(self, model,
**hints):
return
random.choice(['replica_1', 'replica_2'])
def db_for_write(self, model,
**hints):
return
"default"
def allow_relation(self, obj1,
obj2, **hints):
return
True
def allow_migrate(self, db,
app_label, model_name=None, **hints):
return
True
- Problemes / Problems
- Lectura just després de l'escriptura / Reading just
before writing
- Unit test
- Option 1: when testing replicas
- Tests
and multiple databases
- NOT WORKING:
- Running
Tests When Replication Is Active
- "You are supposed to be able to mark a
database as a “test mirror”, but this
appears not to work."
- Workaround:
- my_project/test_settings.py
# Extends
the regular settings of the
project.
from .settings import *
# disable readonly replicas, as
they are not working in tests
#
https://andrewbrookins.com/python/scaling-django-with-postgres-read-replicas/#running-tests-when-replication-is-active
DATABASE_ROUTERS = []
- python manage test --settings
my_project.tests_settings
- TEST_MIRROR
setting doesn't work as expected (and has no
tests)
- settings.py
DATABASES = {
'replica_1': {
...
'TEST': {
'MIRROR': 'default',
}
}
}
- Option 2: force to use only one database (e.g.
'default'):
- ...
- AWS Aurora
- Debug
- get instance:
User.objects.all()
User.objects.using('replica_1').all()
User.objects.using('replica_2').all()
...
- save instance
my_user.save()
my_user.save(using='default')
my_user.save(using='replica_1')
- given an instance, get the database it is stored in:
- ...
- Migració / Migration (e.g. from SQLite to MariaDB)
- with original database (e.g. SQLite)
python manage.py dumpdata
[--exclude=auth] --exclude=contenttypes
--exclude=auth.permission > dump_$(date
'+%Y%m%d_%H%M').json
- change DATABASE in your settings.py
python
manage.py syncdb
python manage.py migrate
python manage.py loaddata
dump.json
- Problemes / Problems
- Migration (model modification)
-
note: in Django 1.7, an app will be affected by migrations
only if it contains a "migrations" directory with an empty
"__init__.py" file in it
- Info
- migrate
- Problemes / Problems
./manage.py migrate my_app xxxx
psycopg2.errors.UndefinedColumn: column
my_app_my_model.my_field does not exist
- when creating a database from scratch
- my_field is not explicitly referenced in xxx
migration, but it was created in a more recent
migration yyyy > xxxx
- but migration xxxx file has "
from
my_app.models import my_model ", and the
code already has a reference to my_field in
class my_model
- Solutions
- temporary solution:
migrate my_app xxxx --fake
- definitive solution: if you do not need
RunPython any more, you can comment it on
xxx...py file:
operations = [
# migrations.RunPython(...)
]
- Squash migrations
- steps:
- squash migrations, keeping old files
./manage.py migrate
./manage.py showmigrations myapp1
./manage.py squashmigrations
myapp1 xxxx
- will create:
myapp1/migrations/0001_squashed_xxxx_auto_yyyymmdd_hhmm.py
./manage.py migrate myapp
- you may need to run:
./manage.py
makemigrations myapp if you are
asked to (e.g. for
models.DateTimeField(auto_now_add=True))
./manage.py showmigrations myapp2
- ...
- commit and release
- deploy changes to all your environments
- deploy files
- on remote (only if some migration files where
created after the squash):
- transition the squashed migration to a normal
migration:
- delete all files it replaces (referenced in
replaces )
- remove "
replaces = [...] "
attribute in the Migration class of the squashed
migration
(myapp1/migrations/0001_squashed_xxxx_auto_yyyymmdd_hhmm.py)
- check "
dependencies = [...] ": you
must replace entries pointing to just removed
files (usually migration files on another
application) by an entry pointing to their
replacement (usually the squashed migration on
the other application)
- commit and release
- deploy changes to all your environments
- deploy files
- no need to run
./manage.py migrate
- Problemes /
Problems
- the generated file gives a syntax error
- RunPython
- Solució / Solution
- if you really don't need a PythonRun
function (e.g. from a set of manual
migrations to include a uuid field), mark it as
elidable (from the origin
migration file):
class
Migration(migrations.Migration):
...
operations = [
migrations.RunPython(myfunction,
reverse_code=migrations.RunPython.noop,
elidable=True),
]
- CircularDependencyError
- ...
- Django
>=1.7
migrations (django.db.migrations)
- Upgrading
from
South
- settings.py
INSTALLED_APPS = (
...
#'south',
)
cd migration
rm -f ????_* __init__.pyc
python manage.py makemigrations
python manage.py migrate
- First time
- create database (for base Django):
- start migrations (for applications):
python manage.py makemigrations
python manage.py migrate
- create a superuser:
- Next times
- modify your model
python manage.py makemigrations [my_app]
[python manage.py migrate [my_app] --list]
python manage.py migrate [my_app]
- Squashing
migrations
python manage.py squashmigrations my_app
0004
- Unapply migrations
- revert to status until migration 0007 (but no
further)
python manage.py migrate my_app 0007
- revert to status previous to initial:
python manage.py migrate zero
- Restart from scratch
- django-reset-migrations
- for each application, reset its migrations:
./manage.py reset_migrations
my_app_1
- if you get an error:
django.db.migrations.exceptions.NodeNotFoundError:
Migration my_other_app_related_to_1 ...
./manage.py reset_migrations
my_other_app_related_to_1
./manage.py reset_migrations
my_app_2
- if you get an error:
django.db.migrations.exceptions.NodeNotFoundError:
Migration my_other_app_related_to_2 ...
./manage.py reset_migrations
my_other_app_relatec_to_2
- ...
- for each application where you got an error,
regenerate its migrations:
./manage.py makemigrations my_app_1
./manage.py makemigrations my_app_2
...
- fake migrate to initial:
./manage.py migrate --fake-initial
- How
to Reset Migrations
- scenario 1: remove database
- remove and create database (PostgreSQL)
sudo su - postgres
database_name="..."
dropdb ${database_name}
- cre
atedb
${database_name}
- migrate
./manage.py migrate
- create admin user
./manage createsuperuser
- scenario 2: keep database
- update database:
./manage.py makemigrations
./manage.py migrate
- clear migration for each app:
applications=$(./manage.py
showmigrations 2>/dev/null | awk
'/^\S/')
for app in ${applications}; do
./manage.py migrate --fake ${app}
zero; done
./manage.py showmigrations
- remove migration files (but protect those
in your virtualenv path, e.g. under dir
"env"):
find . -not -path "./env*" -path
"*/migrations/*.py" -not -name
"__init__.py" -delete
find . -not -path "./env*" -path
"*/migrations/*.pyc" -delete
- create the initial migration:
./manage.py makemigrations
- fake the initial migration:
./manage.py migrate
--fake-initial
./manage.py showmigrations
- to remove all migrations and start from scratch:
- remove the project database:
mysql -P 3307 -u root -p
DROP DATABASE myprojectdb;
- create the database:
mysql -P 3307 -u root -p
CREATE DATABASE myprojectdb;
GRANT ALL ON myprojectdb.* TO
'myuser'@'localhost' IDENTIFIED BY
'mypassword';
FLUSH PRIVILEGES;
- cd my_app/migrations
- rm -f ????_*
- cd ../..
- python manage.py migrate
- python manage.py makemigrations
- python manage.py migrate
- South
- Instal·lació / Installation
pip install South
- settings.py
INSTALLED_APPS = (
...
'south',
)
python manage.py syncdb (to generate
South own tables)
- Primera vegada / First time
- brand new app (no syncdb has been executed):
python manage.py schemamigration
toto_app --auto
python manage.py migrate
toto_app
- existing apps (syncdb has been executed and
therefore tables has been created) and VCS (e.g.
git, subversion...) (Converting
an
app)
- en el propi ordinador / in own computer
python
manage.py convert_to_south
toto_app
python manage.py schemamigration
toto_app --initial
python manage.py
migrate toto_app 0001 --fake
- en altres ordinadors / in other computers (VCS
up to date)
python manage.py
migrate toto_app 0001 --fake
- Següents vegades / Next times
- modify your model
python manage.py
schemamigration my_app --auto
python manage.py
migrate my_app (instead of python manage.py
syncdb)
- if further modifications are done in your model,
and you don't want to generate another migration:
python manage.py
schemamigration my_app --auto --update
python manage.py
migrate my_app
- Llista de migracions / List migrations:
python manage.py migrate --list
- Evolution
- SQL
- No SQL
|
|
- How
to
deploy with WSGI
- Django documentation
- Deploying
static
files
- Cache
- Django’s
cache framework
- Tipus / Types
- Nivells / Levels
-
|
level
|
mechanism
|
Django
|
server |
The
per-site cache |
|
- settings.py
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
# sets HTTP
headers :
Expires: current date + CACHE_MIDDLEWARE_SECONDS ,
Cache-Control: max_age=CACHE_MIDDLEWARE_SECONDS
'django.middleware.common.CommonMiddleware',
...
'django.middleware.cache.FetchFromCacheMiddleware',
]
CACHE_MIDDLEWARE_ALIAS
CACHE_MIDDLEWARE_SECONDS
(overwritten by max_age in
cache_control)
CACHE_MIDDLEWARE_KEY_PREFIX
|
The
per-view cache
|
|
- urls.py
from
django.views.decorators.cache
import cache_page
url(r'^myview/$', cache_page(60
* 15)(views.MyView.as_view()),
name='my-detail' ),
- views.py
from
django.views.decorators.cache
import cache_page
@cache_page(60*2)
def my_view(request, ...):
from rest_framework_extensions.cache.decorators
import cache_response
class
MyModelViewSet(viewsets.ModelViewSet):
@cache_response( settings.CACHE_TIMEOUT_MYMODEL_LISTVIEW,
key_func=MyKeyConstructor() )
def list(self,
request, *args, **kwargs):
return
viewsets.ModelViewSet.list(self,
request, *args, **kwargs)
@cache_response(settings.CACHE_TIMEOUT_MYMODEL_MYDETAILVIEW,
key_func=MyKeyConstructor())
@action(...)
def
my_custom_view(self, request,
*args, **kwargs)
...
- NOTE: @cache_response does not
work with @api_view. Apply it to a
method (e.g. get()) in views.APIView instead
from
rest_framework_extensions.cache.decorators
import cache_response
class
MyCustomAPIView(views.APIView):
@cache_response(settings.CACHE_TIMEOUT_MYCUSTOM_APIVIEW,
key_func=MyKeyConstructor())
def
get(self, request, *args,
**kwargs):
...
|
cache only pieces |
|
|
client |
downstream
- browser-based cache
- squid
|
HTTP cache
headers
|
- views.py
from
django.views.decorators.cache
import cache_control
@ cache_control (max_age=settings.CACHE_TIMEOUT_ MYMODEL_MYDETAILVIEW )
|
- Buida la cache / Purge cache
./manage.py shell
from django.core.cache import cache; cache.clear()
- Exemples / Examples
- settings.py
CACHES
= {
'default': {
'BACKEND':
'django.core.cache.backends.memcached.MemcachedCache',
#'LOCATION':
'unix:/var/run/memcached/memcached.sock',
'LOCATION': '127.0.0.1:11211',
#'TIMEOUT': 60 * 5,
'VERSION': 1,
}
}
- djangorestframework
- only for a specific view
urls.py
from
django.views.decorators.cache import
cache_page
urlpatterns = patterns('',
url(r'^myview/$', cache_page(60
* 15)(views.MyView.as_view()),
name='my-detail' ),
)
- Viewset
- DRF-Extensions
- request header:
Cache-Control:
no-cache
- views.py
from
django.views.decorators.cache import cache_control
from
rest_framework_extensions.cache.decorators
import (
cache_response
)
from
rest_framework_extensions.key_constructor.constructors
import (
DefaultKeyConstructor
)
from
rest_framework_extensions.key_constructor.bits
import (
KwargsKeyBit,
)
class MyKeyConstructor(DefaultKeyConstructor):
# this is needed to
differentiate responses that have a
parameter inside url (internally is a
kwarg)
#
.../<some_args>/my_detail_view/
kwargs = KwargsKeyBit()
class
MyModelViewSet(viewsets.ModelViewSet):
@ cache_control (max_age=settings.CACHE_TIMEOUT_ MYMODEL_MYDETAILVIEW )
@cache_response(settings.CACHE_TIMEOUT_MYMODEL_MYDETAILVIEW,
key_func=MyKeyConstructor())
@detail_route(
serializer_class=MyDetailViewSerializer
)
def
my_detail_view(self, request, *args,
**kwargs):
...
- Django
book
- MS Windows
- A
Comparison of Web Servers for Python Based Web Applications
- Timeout
|
AWS load balancer |
nginx |
uwsgi |
AWS S3 storage |
config |
Description
|
nginx.conf
location / {
...
uwsgi_read_timeout 4000s;
}
|
/etc/uwsgi/vassals/myvassal.ini
[uwsgi]
...
# Set internal sockets timeout in seconds.
# to avoid error "timeout during read(65536) on
wsgi.input"
socket-timeout = 300
|
s3utils.py
from
storages.backends.s3boto3 import S3Boto3Storage
from django.utils.deconstruct import
deconstructible
from botocore.config import Config
@deconstructible
class MyS3Storage(S3Boto3Storage):
# Possible solution to 504s on
long-timed upload requests proposed by:
# https://github.com/jschneier/django-storages/issues/279#issuecomment-522063957
config = Config(
connect_timeout=4000,
read_timeout=4000,
retries={
'max_attempts': 20
}
)
|
raised error when
timeout is reached |
AWS load balancer logs:
|
|
django logs:
|
504 Gateway Timeout
|
|
|
|
|
|
- Resum de permisos d'execució /
Execution permission summary
|
|
|
user
|
access
|
|
django execution from
|
service script (systemd)
|
default user
|
user specification at
|
suggested value
|
must have access to
|
specified at |
suggested value
|
permissions
|
CLI
|
manage.py migrate
|
|
|
|
|
django logger files |
myproject/myproject/settings.py
(GroupWriteRotatingFileHandler) |
/var/log/django/myproject.log |
useradd django
usermod -a -G django centos
mkdir -p /var/log/django
chown django.django /var/log/django/
chmod g+ws /var/log/django
|
service
|
nginx
|
/usr/lib/systemd/system/nginx.service
|
|
/etc/nginx/nginx.conf
|
user nginx;
|
log files
|
|
/var/log/nginx/
|
|
|
|
uwsgi
sock
files |
/etc/nginx/conf.d/myproject_nginx.conf |
server
unix:///var/lib/uwsgi/myproject.sock; |
(nginx
SELinux)
|
uwsgi |
/usr/lib/systemd/system/emperor.uwsgi.service
|
root.root
|
/etc/uwsgi/emperor.ini
|
uid
=
django
gid = django
|
/etc/uwsgi/vassals/myproject.ini
|
socket =
/var/lib/uwsgi/myproject.sock
chmod-socket = 666
|
mkdir
/var/lib/uwsgi/
chown django.django /var/lib/uwsgi/
|
|
|
django logger files
|
myproject/myproject/settings.py |
/var/log/django/myproject.log
|
(mireu la primera fila a
sobre / see first row above)
|
celery
|
/usr/lib/systemd/system/celery.service
|
|
/usr/lib/systemd/system/celery.service
|
User=django
Group=django
|
pid file |
/etc/sysconfig/celery
|
/var/run/celery/%N.pid
|
|
log file |
/etc/sysconfig/celery |
/var/log/celery/%N.log |
|
django logger files |
myproject/myproject/settings.py |
|
|
celerybeat |
/usr/lib/systemd/system/celerybeat.service |
|
/usr/lib/systemd/system/celerybeat.service |
User=django
Group=django |
pid file |
/etc/sysconfig/celery |
/var/run/celery/beat.pid
|
|
log file |
/etc/sysconfig/celery |
|
|
- Resum de
desplegament de diversos projectes en un únic servidor
-
|
service
|
common |
projects
|
nginx
|
/usr/lib/systemd/system/nginx.service
[Unit]
Description=The nginx HTTP and reverse proxy
server
After=network.target remote-fs.target
nss-lookup.target
[Service]
Type=forking
PIDFile=/run/nginx.pid
# Nginx will fail to start if /run/nginx.pid
already exists but has the wrong
# SELinux context. This might happen when
running `nginx -t` from the cmdline.
#
https://bugzilla.redhat.com/show_bug.cgi?id=1268621
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=process
PrivateTmp=true
[Install]
WantedBy=multi-user.target
|
/etc/nginx/nginx.conf
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log;
pid
/run/nginx.pid;
events {
worker_connections
1024;
}
http {
include
/etc/nginx/mime.types;
default_type
application/octet-stream;
log_format main
'$remote_addr - $remote_user [$time_local]
"$request" '
'$status
$body_bytes_sent "$http_referer" '
'"$http_user_agent"
"$http_x_forwarded_for"';
access_log
/var/log/nginx/access.log main;
sendfile
on;
#tcp_nopush on;
# The Case of the
Mysterious AWS ELB 504 Errors
# https://sigopt.com/blog/the-case-of-the-mysterious-aws-elb-504-errors/
client_header_timeout =
10s;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
index
index.html index.htm;
# Load modular
configuration files from the /etc/nginx/conf.d
directory.
include
/etc/nginx/conf.d/*.conf
}
|
/etc/nginx/conf.d/first-project.conf
# the upstream
component nginx needs to connect to
upstream first-project
{
server
unix:///var/lib/uwsgi/first-project.sock;
# for a file socket
}
# configuration of the server
server {
# the port your site will
be served on
listen 80;
# the domain name it will
serve for
server_name first-project.example.org;
charset utf-8;
# max upload size
client_max_body_size
75M; # adjust to taste
# Django media
location /media {
alias /path/to/first-project/media;
# your Django project's media files - amend as
required
}
location /static {
alias /path/to/first-project /collected_static;
# your Django project's static files - amend
as required
}
# letsencrypt
location /.well-known {
alias /home/myuser/.well-known;
}
# Finally, send all
non-media requests to the Django server.
location / {
uwsgi_pass first-project;
#
to avoid timeout when requested by backend
uwsgi_read_timeout 180s;
#include
/home/username/src/uwsgi-tutorial/mysite/uwsgi_params;
# the uwsgi_params file you installed
include /etc/uwsgi/uwsgi_params;
}
}
/etc/nginx/conf.d/second-project.conf
|
uwsgi
|
/usr/lib/systemd/system/emperor.uwsgi.service
[Unit]
Description=uWSGI Emperor
After=syslog.target
[Service] # yum install uwsgi
uwsgi-plugin-python2 ExecStart=/usr/sbin/uwsgi
--ini /etc/uwsgi/emperor.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all
[Install]
WantedBy=multi-user.target
|
/etc/uwsgi/emperor.ini
[uwsgi]
emperor = /etc/uwsgi/vassals
uid = django
gid = django
|
/etc/uwsgi/vassals/first-project.ini
[uwsgi]
chdir = /path/to/first-project
module = first_project.wsgi
home = /path/to/first-project/env
master = true
processes = 10
socket = /var/lib/uwsgi/first-project.sock
chmod-socket = 666
vacuum = true
plugin = python
/etc/uwsgi/vassals/second-project.ini
|
celery
|
/usr/lib/systemd/system/celery.service
[Unit]
Description=Celery workers
After=network.target rabbitmq-server.service
[Service]
Type=forking
# User and Group cannot be specified at
EnvironmentFile
User=django
Group=django
EnvironmentFile=-/etc/sysconfig/celery
# run ExecStartPre as priviledged user and set
up /var/run
PermissionsStartOnly=true
#ExecStartPre=/usr/sbin/useradd celery
ExecStartPre=-/usr/bin/mkdir -p
${CELERYD_PID_DIR}
ExecStartPre=/usr/bin/chown -R django:django
${CELERYD_PID_DIR}
ExecStartPre=-/usr/bin/mkdir -p
${CELERYD_LOG_DIR}
ExecStartPre=/usr/bin/chown -R django:django
${CELERYD_LOG_DIR}
# same value as ${CELERYD_PID_FILE}, but must
be specified.
# needed to avoid some immediate stops of the
service
PIDFile=/var/run/celery/w1.pid
# executable cannot be specified at
EnvironmentFile
ExecStart=/path/to/first-project/env/bin/python
-m celery multi start ${CELERYD_NODES} \
-A ${CELERY_APP} \
--workdir=${CELERYD_CHDIR}
\
--pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} \
--loglevel="${CELERYD_LOG_LEVEL}" \
${CELERYD_OPTS}
ExecStop= /path/to/first-project/ env/bin/python
-m celery multi stopwait ${CELERYD_NODES} \
--pidfile=${CELERYD_PID_FILE}
ExecReload= /path/to/first-project/ env/bin/python
-m celery multi restart ${CELERYD_NODES} \
-A ${CELERY_APP} \
--workdir=${CELERYD_CHDIR}
\
--pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} \
--loglevel="${CELERYD_LOG_LEVEL}" \
${CELERYD_OPTS}
[Install]
WantedBy=multi-user.target
/usr/lib/systemd/system/celerybeat.service
[Unit]
Description=Celery beat scheduler
After=network.target
[Service]
Type=simple
User=django
Group=django
EnvironmentFile=-/etc/sysconfig/celery
#WorkingDirectory=$CELERYD_CHDIR
# run ExecStartPre as priviledged user and set
up /var/run
PermissionsStartOnly=true
#ExecStartPre=/usr/sbin/useradd django
ExecStartPre=-/usr/bin/mkdir -p
${CELERYBEAT_PID_DIR}
ExecStartPre=/usr/bin/chown -R django:django
${CELERYBEAT_PID_DIR}
ExecStartPre=-/usr/bin/mkdir -p
${CELERYBEAT_LOG_DIR}
ExecStartPre=/usr/bin/chown -R django:django
${CELERYBEAT_LOG_DIR}
# user django has to have access to
/path/to/first-project/env/bin/python
#ExecStartPre=/usr/bin/chmod ag+xr -R
/home/wct/
ExecStart=/path/to/first-project/env/bin/python
-m celery beat \
-A ${CELERY_APP} \
--workdir=${CELERYBEAT_CHDIR} \
--pidfile=${CELERYBEAT_PID_FILE} \
--logfile=${CELERYBEAT_LOG_FILE} \
--loglevel=${CELERYBEAT_LOG_LEVEL} \
--schedule=${CELERYBEAT_SCHEDULE}
ExecStop=/bin/systemctl kill
celerybeat.service
[Install]
WantedBy=multi-user.target
|
/etc/sysconfig/celery
# this file must be
installed as /etc/sysconfig/celery
## common worker and beat settings
# Absolute or relative path to the 'celery'
command:
#CELERY_BIN="/home/centos/my_project/env/bin/python
-m celery"
# App instance to use
# comment out this line if you don't use an
app
# directory name where celery.py resides
CELERY_APP="first-project"
# Workers should run as an unprivileged user.
# You need to create this user
manually (or you can choose
# a user/group combination that
already exists, e.g. nobody).
#CELERYD_USER="celery"
#CELERYD_GROUP="celery"
# not working?
d /var/run/celery 0755 celery celery -
d /var/log/celery 0755 celery celery -
## Worker settings
CELERYD_NODES="w1"
CELERYD_OPTS="--time-limit=300
--concurrency=8"
CELERYD_LOG_DIR="/var/log/celery"
CELERYD_LOG_FILE="/var/log/celery/%N.log"
CELERYD_LOG_LEVEL="DEBUG"
CELERYD_PID_DIR="/var/run/celery"
CELERYD_PID_FILE="/var/run/celery/%N.pid"
CELERYD_CHDIR=/path/to/first-project/
## Beat settings
CELERYBEAT_LOG_DIR="/var/log/celery"
CELERYBEAT_LOG_FILE="/var/log/celery/beat.log"
CELERYBEAT_LOG_LEVEL="DEBUG"
CELERYBEAT_PID_DIR="/var/run/celery"
CELERYBEAT_PID_FILE="/var/run/celery/beat.pid"
CELERYBEAT_SCHEDULE="/var/run/celery/celerybeat-schedule"
CELERYBEAT_CHDIR=/path/to/first_project/
DJANGO_SETTINGS_MODULE="first_project.settings"
|
|
- Resum de serveis i
configuracions:
|
|
|
server
|
|
mysite/settings.py
|
wsgi.py / asgi.py
|
standalone
|
service
|
nginx
|
WSGI
|
|
mysite/mysite/wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'mysite.settings')
application = get_wsgi_application()
|
uWSGI
uwsgi -i /etc/uwsgi/emperor.ini
uwsgi --emperor /etc/uwsgi/vassals --uid
django --gid django
|
- /usr/lib/systemd/system/emperor.uwsgi.service
[Unit]
Description=uWSGI Emperor
After=syslog.target
[Service]
#ExecStart=/path/to/mysite/env/bin/uwsgi --ini
/etc/uwsgi/emperor.ini # when
using system-installed uwsgi (yum install
uwsgi): ExecStart=/usr/sbin/uwsgi
--ini /etc/uwsgi/emperor.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all
[Install]
WantedBy=multi-user.target
- /etc/uwsgi/emperor.ini
[uwsgi]
emperor = /etc/uwsgi/vassals
uid = django
gid = django
- /etc/uwsgi/vassals/mysite.ini
# mysite_uwsgi.ini
file
[uwsgi]
# Django-related settings
# the base directory (full path)
chdir
= /path/to/mysite
# Django's wsgi file (mysite/wsgi.py)
module
= mysite.wsgi
# for weblate, use wsgi-file instead of
module:
#wsgi-file
= /path/to/weblate/weblate/wsgi.py
# the virtualenv (full path)
#home
= /opt/p27
home
= /path/to/mysite/env
# process-related settings
# master
master
= true
# maximum number of worker processes
processes
= 10
# the socket (use the full path to be safe)
socket
= /var/lib/uwsgi/mysite.sock
# ... with appropriate permissions - may be
needed
chmod-socket = 666
# clear environment on exit
vacuum
= true
# when using system-installed uwsgi (yum
install uwsgi-plugin-python2)
#plugins = python
# when using
system-installed
uwsgi (yum install uwsgi-plugin-python36)
plugins = python36
|
- /etc/nginx/conf.d/mysite.conf
# the upstream
component nginx needs to connect to
upstream django_mysite {
server
unix:///var/lib/uwsgi/mysite.sock; # for a
file socket
#server
unix:///home/username/src/uwsgi-tutorial/mysite/mysite.sock;
# for a file socket
#server 127.0.0.1:8001; #
for a web port socket (we'll use this first)
}
# configuration of the server
server {
# the port your site will
be served on
listen 8000;
# the domain name it will
serve for
#server_name .example.com;
# substitute your machine's IP address or FQDN
charset utf-8;
# max upload size
client_max_body_size
75M; # adjust to taste
# Django media
location /media {
alias /path/to/your/mysite/media; # your
Django project's media files - amend as
required
}
location /static {
alias /path/to/your/mysite/static; # your
Django project's static files - amend as
required
}
# Finally, send all
non-media requests to the Django server.
location / {
uwsgi_pass django_mysite;
#
to avoid message: "504 Gateway Timeout"
# if
you are using AWS ELB, check also its idle
timeout
uwsgi_read_timeout 180s;
#include
/home/username/src/uwsgi-tutorial/mysite/uwsgi_params;
# the uwsgi_params file you installed
include /etc/uwsgi/uwsgi_params
}
}
|
Gunicorn |
|
|
ASGI
|
ASGI_APPLICATION =
"mysite_ws.routing.application"
# optional:
CHANNEL_LAYERS = {
"default": {
"BACKEND":
"channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("redis-server-name", 6379)],
},
},
}
|
mysite_ws/mysite_ws/asgi.py
import os
import django
from channels.routing import
get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"mysite_ws.settings")
django.setup()
application = get_default_application()
|
Daphne
daphne -p 9000 myproject.asgi:application
daphne -b 0.0.0.0 -p 9000
myproject.asgi:application
daphne -e
ssl:443:privateKey=key.pem:certKey=crt.pem
django_project.asgi:application
|
- /usr/lib/systemd/system/asgi-myproject.service
[Unit]
Description=daphne daemon
After=network.target
[Service]
PIDFile=/run/daphne/pid
User=root
Group=root
WorkingDirectory=/path/to/mysite_ws
ExecStart=/path/to/mysite_ws/env/bin/daphne
--bind 0.0.0.0 --port 9000 --verbosity 0
mysite_ws.asgi:application
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
Restart=on-abort
PrivateTmp=true
[Install]
WantedBy=multi-user.target
|
- /etc/nginx/conf.d/mysite.conf
upstream
channels-backend {
server localhost:9000;
}
# http and websockets served by daphne
server {
listen 80;
server_name localhost;
location / {
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_pass http://channels-backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For
$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host
$server_name;
}
}
upstream
channels-backend {
server localhost:9000;
}
# https served by uwsgi, websockets
served by daphne
server {
listen 80;
server_name localhost;
location /ws/ {
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_pass http://channels-backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For
$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host
$server_name;
}
location / {
uwsgi_pass django_mysite;
...
}
}
|
- Option 1: Start the development
http server
cd djcode/mysite
python manage.py runserver
[[0.0.0.0]:8080]
- Option 1.2: django-devserver
- https development server
- Option 2: Integration with Apache + mod_wsgi:
- How to use Django with Apache and mod_wsgi (1.4)
(1.6)
- Install
Apache
and mod_wsgi (1.3)
- Newbie
Mistakes (Django)
- Serving
favicon
in an Django App using Apache
- Designating
the
settings
- Steps to setup (see also: Production,
script for Apache
installation):
- make sure that your mod_wsgi version matches the
Python version:
- ldd /etc/httpd/modules/mod_wsgi.so
- (encara cal?/still needed?) modify
/etc/httpd/modules.d/B23_mod_wsgi.conf
(Mageia), /etc/httpd/ conf.d/wsgi.conf
(CentOS) , or /etc/apache2/mods-available/wsgi.conf
(Debian, Ubuntu):
#WSGIScriptAlias /
/home/user/src/djcode_1.4/mysite/mysite/wsgi.py #
only when running a single django:
# parent directory of parent
directory (grandparent) of
settings.py:
WSGIPythonPath
/home/user/src/djcode_1.4/mysite/
# if
you are using virtualenv , replace
the previous line by this one:
# WSGIPythonPath
/home/user/src/djcode_1.4/mysite/:/path/to/your/venv/lib/python2.X/site-packages
- Note: for multiple
instances on Apache (wsgi)
- Basic
configuration
- first_site.conf
# only when running a
single django:
# parent directory of parent directory
(grandparent) of settings.py, and virtualenv
path:
#WSGIPythonPath /home/ubuntu/my_project1/ :/path/to/your/venv/lib/python2.X/site-packages
<VirtualHost *:8000>
# to run several djangos
WSGIDaemonProcess example1.com
python-path=/home/ubuntu/my_project1/:/path/to/your/venv/lib/python2.X/site-packages
WSGIProcessGroup example1.com
</VirtualHost>
- second_site.conf
# only when running a
single django:
# parent directory of parent directory
(grandparent) of settings.py, and virtualenv
path:
#WSGIPythonPath /home/ubuntu/my_project2/ :/path/to/your/venv/lib/python2.X/site-packages
<VirtualHost *:8008>
# to run several djangos
WSGIDaemonProcess example2.com
python-path=/home/ubuntu/my_project2/
:/path/to/your/venv/lib/python2.X/site-packages
WSGIProcessGroup example2.com
</VirtualHost>
- Deploying multiple
django apps on Apache with mod_wsgi
/etc/httpd/modules.d/B23_mod_wsgi.conf
WSGIPythonPath
/var/www/site1:/var/www/site2
- create the file
/etc/{httpd,apache2}/conf/sites-available/django_project
(to avoid "Access forbidden" Error 403) or /etc/httpd/conf/vhosts.d/django_project.conf
Listen 8008
# parent directory of parent directory
(grandparent) of settings.py:
# IMPORTANT: if
using virtualenv, don't
put python-path here; put it in wsgi.conf ( WSGIPythonPath).
WSGIPythonPath
/home/user/src/djcode_1.4/mysite/
<VirtualHost *:8008>
#WSGIDaemonProcess python-path=
/home/user/src/djcode_1.4/mysite/
# to avoid "Authentication credentials were
not provided" error message
# with django-rest-framework
WSGIPassAuthorization On
# full path of wsgi.py:
WSGIScriptAlias /
/home/user/src/djcode_1.4/mysite/mysite/wsgi.py
# parent directory of wsgi.py:
<Directory
/home/user/src/djcode_1.4/mysite/mysite>
<Files wsgi.py>
Order
deny,allow
Allow
from all
</Files>
</Directory>
</VirtualHost>
Alias
/media/
/path/to/media/
<Directory /path/to/media/>
Order deny,allow
Allow from all
</Directory>
- Comproveu els permisos
de grup del fitxer de la base de dades i els seus
directoris per sobre / Check groups
permissions of database file and their ancestor
directories:
- if database is in a system
directory:
chown
apache.apache -R /var/www/django_dir /
chgrp `grep "^Group"
/etc/httpd/conf/httpd.conf | awk '{print $2}'`
-R /var/www/django_dir/
chmod g+w -R /var/www/django_dir/
- if database is in a user
directory:
- add group
www-data or apache
(or whatever defined in Group
directive in httpd.conf) as a secondary group of
the user
that owns the directory:
sudo chown www-data. -R polls
- maybe changing only the group is enough:
- CentOS:
# chgrp apache service.db
- Ubuntu:
sudo chgrp www-data service.db
sudo chmod 664 service.db
- (needed? no)
modify wsgi.py:
import sys
sys.path.append('/home/user/src/djcode_1.4/mysite/')
- Install and enable mod_wsgi:
- Mageia
urpmi apache-mod_wsgi
- httpd.conf (cal?/needed?)
LoadModule wsgi_module
extramodules/mod_wsgi.so
cd /etc/httpd/conf/vhosts.d; ln -s
../sites-available/django-site
01_django-site.conf
- Ubuntu
sudo apt-get install libapache2-mod-wsgi
cd /etc/apache2/mods-enabled; ln -s
../mods-available/wsgi.conf .; ln -s
../mods-available/wsgi.load .
cd /etc/apache2/conf/sites-enabled; ln
-s ../sites-available/django-site .
- CentOS
- Copieu els
fitxers estàtics / Copy the static files
- Media files
- Django
settings.py (IMPORTANT:
path must be absolute; constructions like "os.path.abspath(... "
will NOT work (but they did for debugging http Django
server))
- Script
for Apache installation:
- django_project/install_lamp/django_project.conf (or
use script create_apache_conf.sh)
Listen 8008
# parent directory of parent directory
(grandparent) of settings.py, and virtualenv path:
WSGIPythonPath
/var/www/django_project/:/opt/PYTHON27/lib/python2.7/site-packages/
<VirtualHost *:8008>
#WSGIDaemonProcess
python-path=/home/user/src/djcode_1.4/mysite/
# to avoid "Authentication credentials
were not provided" error message
# with django-rest-framework
WSGIPassAuthorization On
# full path of wsgi.py:
WSGIScriptAlias /
/var/www/django_project/django_project/wsgi.py
# parent directory of wsgi.py:
<Directory
/var/www/django_project/django_project/>
<Files wsgi.py>
#Order
deny,allow
#Allow
from all
Require
all granted
</Files>
</Directory>
# static
files
# Alias
<settings.STATIC_URL>
<settings.STATIC_ROOT>
#AliasMatch ^/([^/]*\.css)
/var/www/django_project/collected_static/styles/$1
Alias /static/
/var/www/django_project/collected_static/
<Directory
/var/www/django_project/collected_static/>
#Order deny,allow
#Allow from all
Require all
granted
</Directory>
# media
files
# Alias
<setttings.MEDIA_URL>
<settings.MEDIA_ROOT>
Alias /mm/ /var/www/django_project/media/
<Directory
/var/www/django_project/media/>
#Order deny,allow
#Allow from all
Require all
granted
</Directory>
</VirtualHost>
- django_project/install_lamp/create_apache_conf.sh
#!/bin/bash
EXPECTED_ARGS=2
if [ $# -ne $EXPECTED_ARGS ]
then
cat <<EOF
Usage: `basename $0` base_dir project_name
Examples:
`basename $0` /var/www/ project_name
`basename $0` /home/user/src/ project_name
EOF
exit 1
fi
BASE_DIR=$1
PROJECT_NAME=$2
VIRTUALENV_DIR=/opt/PYTHON27/lib/python2.7/site-packages/
LISTEN_PORT=80
if [ ${LISTEN_PORT} -eq 80 ]
then
cat > ${PROJECT_NAME}.conf
<<EOF
EOF
else
cat > ${PROJECT_NAME}.conf
<<EOF
Listen ${LISTEN_PORT}
EOF
fi
cat >> ${PROJECT_NAME}.conf <<EOF
# parent directory of parent directory
(grandparent) of settings.py, and virtualenv path:
WSGIPythonPath
${BASE_DIR}${PROJECT_NAME}/:${VIRTUALENV_DIR}
<VirtualHost *:${LISTEN_PORT}>
#WSGIDaemonProcess
python-path=/home/user/src/djcode_1.4/mysite/
# full path of wsgi.py:
WSGIScriptAlias /
${BASE_DIR}${PROJECT_NAME}/${PROJECT_NAME}/wsgi.py
# parent directory of wsgi.py:
<Directory
${BASE_DIR}${PROJECT_NAME}/${PROJECT_NAME}/>
<Files wsgi.py>
#Order
deny,allow
#Allow
from all
Require
all granted
</Files>
</Directory>
# static files
#AliasMatch ^/([^/]*\.css)
${BASE_DIR}${PROJECT_NAME}/collected_static/styles/$1
Alias /static/
${BASE_DIR}${PROJECT_NAME}/collected_static/
<Directory
${BASE_DIR}${PROJECT_NAME}/collected_static/>
#Order deny,allow
#Allow from all
Require all granted
</Directory>
# media files
Alias /media/
${BASE_DIR}${PROJECT_NAME}/media/
<Directory
${BASE_DIR}${PROJECT_NAME}/media/>
#Order deny,allow
#Allow from all
Require all granted
</Directory>
</VirtualHost>
EOF
- django_project/install_lamp/install_apache.sh
#!/bin/bash
EXPECTED_ARGS=3
if [ $# -ne $EXPECTED_ARGS ]
then
cat <<EOF
Usage: `basename $0` project_name
source_webapp_dir destination_webapp_dir
Examples:
`basename $0` django_project ~user/src/ /var/www/
`basename $0` django_project ~user/src/ ~user/src/
Actions:
- copy project_name.conf to apache configuration
directory (e.g. /etc/httpd/conf/webapps.d/)
Make sure that it points to correct
directories: destination_webapp_dir
- recursively copy source_webapp_dir/project_name
to destination_webapp_dir/project_name, only if
they are different
- change the owner and group of the files and
directories to the right ones
The following typical OS are automatically
detected:
- Red Hat, CentOS, Mageia, ...
- Debian, Ubuntu, ...
- MAC OSX (MAMP)
EOF
exit 1
fi
project_name=$1
source_webapp_dir=$2
destination_webapp_dir=$3
#webapp_dir=/var/www/${project_name}
virtual_env_source=/opt/PYTHON27/
is_debian=0
# Red Hat, CentOS, Mageia, ...
if [ -f /etc/httpd/conf/httpd.conf ]
then
httpd_config=/etc/httpd/conf/httpd.conf
webapp_conf_dir=/etc/httpd/conf/webapps.d/
usuari_httpd=`grep "^Group"
$httpd_config | awk '{print $2}'`
grup_httpd=`grep "^User"
$httpd_config | awk '{print $2}'`
fi
# MAC OSX
if [ -f /Applications/MAMP/conf/apache/httpd.conf
]
then
httpd_config=/Applications/MAMP/conf/apache/httpd.conf
webapp_conf_dir=/Applications/MAMP/conf/apache/extra
webapp_dir=~/sites/${project_name}
fi
# Debian, Ubuntu, ...
if [ -f /etc/apache2/apache2.conf ]
then
is_debian=1
httpd_config=/etc/apache2/apache2.conf
#webapp_conf_dir=/etc/apache2/conf.d/
webapp_conf_dir=/etc/apache2/sites-available/
usuari_httpd=`awk -F=
'/APACHE_RUN_USER/ {print $2}'
/etc/apache2/envvars`
grup_httpd=`awk -F=
'/APACHE_RUN_GROUP/ {print $2}'
/etc/apache2/envvars`
fi
# collect static files
source ${virtual_env_source}bin/activate
python ../manage.py collectstatic
deactivate
# còpia recursiva del virtualenv
# còpia recursiva del project_name
if [ $source_webapp_dir != $destination_webapp_dir
]
then
echo "copying application
$project_name from $source_webapp_dir to
$destination_webapp_dir"
#mkdir -p $webapp_dir
#rsync -a --exclude='.git'
--exclude='install_lamp' .. $webapp_dir
mkdir -p
${destination_webapp_dir}/${project_name}
rsync -a --exclude='.git'
--exclude='install_lamp'
${source_webapp_dir}/${project_name}
${destination_webapp_dir}
fi
# echo "changing permissions for
$destination_webapp_dir"
# #chown ${usuari_httpd}.${grup_httpd} -R
$webapp_dir
# chown ${usuari_httpd}.${grup_httpd} -R
${destination_webapp_dir}
# webapp config
echo "copying config file ${project_name}.conf to
${webapp_conf_dir}"
cp ${project_name}.conf ${webapp_conf_dir}
if [ ${is_debian} -eq 1 ]
then
# disable default site
a2dissite 000-default
# enable added site
a2ensite ${project_name}
# activate new configuration
service apache2 reload
fi
- Problemes / Problems
- CentOS:
Apache
"Access forbidden!" 403
- Comproveu els permisos
d'usuari UNIX del directori / Check the UNIX user
permissions of the directory
- Comproveu que al directori on hi ha
wsgi.py
no s'hi accedeixi mitjançant un enllaç simbòlic
- Solució:
- ? afegir directiva FollowSymbolicLink
"unable to open database file"
Error 500: Server error:
(?) WSGIPythonPath needs to be
specified (on /etc/httpd/modules.d/B23_mod_wsgi.conf
or .../mods-available/wsgi.conf )
Internal Server Error
- /var/log/httpd/error_log
- ImportError: Could not import settings ... (Is
it on sys.path?): No module named ...
- ImportError: No module named bootstrap_toolkit
Authentication credentials were not provided
- Admin: redirect to login page (Sessions)
- Admin:
...
[error] 9989#0: *1346 readv() failed (104:
Connection reset by peer) while reading
upstream, client: 37.15.63.190, server: ...,
request: "POST /admin/... HTTP/1.1", ...
The
number of GET/POST parameters exceeded
settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.
- Solució
/ Solution
- settings.py
DATA_UPLOAD_MAX_NUMBER_FIELDS
= None
- Option 3: nginx + ...
- uWSGI
- websocket
- Permisos
d'execució / Execution permissions
- Instal·lació /
Installation
- des de paquets de la distribució / from
distribution packages
- CentOS/Alma 8
- activate GhettoForge
repo
sudo dnf install
https://mirror.ghettoforge.org/distributions/gf/gf-release-latest.gf.el8.noarch.rpm
sudo dnf config-manager --enable
gf-tesing
- install
sudo dnf install uwsgi
uwsgi-plugin-python3
- CentOS 7
sudo dnf install uwsgi
uwsgi-plugin-python3
- How
to
use Django with uWSGI (Django)
- Setting
up
Django and your web server with uWSGI and nginx
(uWSGI)
- Managing
the
uWSGI server
- Things
to know (best practices and “issues”) READ IT !!!
- without nginx (the web client <-> uWSGI
<-> Python)
wget
https://raw.githubusercontent.com/nginx/nginx/master/conf/uwsgi_params
uwsgi --http :8000 --module mysite.wsgi
- with nginx (the web client <-> the web server
<-> the socket <-> uWSGI <-> Python)
- Setting
up
Django and your web server with uWSGI and nginx
- Permisos
d'execució / Execution permissions
- nginx config
- /etc/nginx/conf.d/mysite_nginx.conf
# the upstream
component nginx needs to connect to
upstream django_mysite {
server unix:///var/lib/uwsgi/mysite.sock;
# for a file socket
#server
unix:///home/username/src/uwsgi-tutorial/mysite/mysite.sock;
# for a file socket
#server
127.0.0.1:8001; # for a web port socket
(we'll use this first)
}
# configuration of the server
server {
# the port your site
will be served on
listen 8000;
# the domain name it
will serve for
#server_name
.example.com; # substitute your machine's
IP address or FQDN
charset utf-8;
# max upload size
client_max_body_size
75M; # adjust to taste
# Django media
location /media {
alias /path/to/your/mysite/media; #
your Django project's media files - amend
as required
}
location /static {
alias /path/to/your/mysite/static; # your
Django project's static files - amend as
required
}
# Finally, send all
non-media requests to the Django server.
location / {
uwsgi_pass django _mysite ;
# to avoid message: "504 Gateway Timeout"
# if you are using AWS ELB,
check also its idle timeout
#
The Case of the Mysterious AWS ELB 504
Errors
# https://sigopt.com/blog/the-case-of-the-mysterious-aws-elb-504-errors/
client_header_timeout = 180s;
uwsgi_read_timeout 180s;
#include
/home/username/src/uwsgi-tutorial/mysite/uwsgi_params;
# the uwsgi_params file you installed
include /etc/uwsgi/uwsgi_params
}
}
- uWSGI config
- Nginx
support
- Instal·lació / Installation
virtualenv
/opt/p27
source
/opt/p27/bin/activate
virtualenv /path/to/mysite/env
source /path/to/mysite/env /bin/activate
pip install uwsgi
- /etc/uwsgi/uwsgi_params
- usage modes
- directly
uwsgi --socket mysite.sock
--module mysite.wsgi
--chmod-socket=666
- from ini config file
- Configuring
uWSGI
to run with a .ini file
- create and give permissions to dir for
socket:
sudo mkdir -p
/var/lib/uwsgi/
sudo chown django.django
/var/lib/uwsgi/
- /path/to/your/mysite/mysite_uwsgi.ini
#
mysite_uwsgi.ini file
[uwsgi]
# Django-related settings
# the base directory (full path)
chdir
= /path/to/mysite
# Django's wsgi file
(mysite/wsgi.py)
module
= mysite.wsgi
# for weblate, use wsgi-file
instead of module: #wsgi-file
= /path/to/weblate/weblate/wsgi.py
# the virtualenv (full path)
#home
=
/opt/p27 home
=
/path/to/mysite/env
# process-related settings
# master
master
= true
# maximum number of worker
processes
processes
= 10
# the socket (use the full path to
be safe)
socket
= /var/lib/uwsgi/mysite.sock
# ... with appropriate permissions
- may be needed
chmod-socket =
666
# clear environment on exit
vacuum
= true
uwsgi -i mysite_uwsgi .ini
- Emperor mode
/etc/uwsgi/vassals /
mysite1_uwsgi.ini ->
/path/to/your/mysite/mysite1_uwsgi.ini
mysite2_uwsgi.ini ->
/path/to/your/mysite/mysite2_uwsgi.ini
...
sudo uwsgi --emperor
/etc/uwsgi/vassals --uid django --gid
django
- Problemes
/ Problems
uwsgi[...]:
bind(): No such file or
directory [core/socket.c line
230]
- check that parent dir of
/var/lib/uwsgi/mysite.sock
exists and has write permissions
for user specified in
/etc/uwsgi/emperor.ini
- Error 502 (from AWS load balancer)
- curl -i ...
HTTP/2 502
server: awselb/2.0
date: Wed, 07 Jun 2023
13:59:46 GMT
content-type: text/html
content-length: 122
- Solució / Solution
- verify target group
associated to load balancer:
at least one instance
(target) should be healthy
- verify that health checks
are received by instance
(security groups, nginx
started with correct listen
port, ...)
- Error
502 (sometimes, under heavy load)
- /var/log/nginx/error.log
[error] 2223#0:
*131046 connect() to
unix:///var/lib/uwsgi/....sock
failed (11: Resource
temporarily unavailable)
while connecting to
upstream
- Solució / Solution
- Error
502
- /var/log/messages (or
journalctl
-f )
uwsgi: --
unavailable modifier
requested: 0 --
- Solució / Solution
- Error
500
- /var/log/messages
uwsgi: --- no python
application found, check
your startup logs for
errors ---
- try manual execution:
sudo systemctl stop
emperor.uwsgi.service
sudo
/path/to/virtualenv/bin/uwsgi
-i
/etc/uwsgi/emperor.ini
sudo
/path/to/virtualenv/bin/uwsgi
--emperor
/etc/uwsgi/vassals --uid
django --gid django
sudo
/path/to/virtualenv/bin/uwsgi
-i /etc/uwsgi/vassals/ mysite1_uwsgi.ini
--uid django --gid django
- verbose
when executing weblate:
django.core.exceptions.ImproperlyConfigured:
Requested
setting BASE_DIR, but
settings are not
configured. You must
either define the
environment variable
DJANGO_SETTINGS_MODULE
or call
settings.configure()
before accessing
settings.
- Solution
- in weblate_uwsgi.ini,
use wsgi-file
instead of
module:
#
Django's wsgi
file
#module
= weblate.wsgi
wsgi-file
=
/path/to/weblate/weblate/wsgi.py
- alternative:
in manual
execution:
sudo
-i
export
DJANGO_SETTINGS_MODULE=weblate.settings;
/path/to/virtualenv/bin/uwsgi
-i
/etc/uwsgi/emperor.ini
OSError:
DATA_DIR
/home/myuser/weblate/weblate/../data
is not writable!
- Solutions
- Install
weblate in an
ad-hoc directory
- Install
weblate inside
home directory
(not
recommended)
- check that
there is a
user
django
and that your
user belongs
to django
group:
useradd
django
usermod
-a -G django
myuser
chown
django.django
-R
/home/myuser/weblate/data
chmod
g+ws /home/myuser/weblate/data
- check ownership
problems (see problems with uwsgi
and httplib2 when user
executing uwsgi and user who
installed pip packages are not
the same):
- change uid, gid in ini
file specified in call to
uwsgi binary (usually from
/usr/lib/systemd/system/emperor.uwsgi.service),
to match username and group
of user who pip-installed
the packages in virtualenv
- start script (systemd)
useradd django
- /etc/uwsgi/emperor.ini
[uwsgi]
emperor = /etc/uwsgi/vassals
uid = django
gid = django
- /etc/systemd/system/emperor.uwsgi.service
[Unit]
Description=uWSGI Emperor
After=syslog.target
[Service]
ExecStart=/path/to/mysite/env/bin/uwsgi
--ini
/etc/uwsgi/emperor.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all
[Install]
WantedBy=multi-user.target
- Problemes / Problems
-
client
error code |
logs
|
info |
solució / solution |
|
/var/log/nginx/error.log |
/var/log/messages
(journalctl
-f) |
django logs |
|
|
415 Unsupported media type
"application/json" in request. |
|
|
|
|
- views.py
- parser_classes=(JSONParser,)
- my_test.py
- res = self.client.post(
...
format="json",
)
|
500 Internal Server Error |
|
uwsgi: --- no python application
found, check your startup logs for errors
--- |
|
|
|
... |
|
uwsgi[...]: bind(): No such file or
directory [core/socket.c line 230] |
|
|
|
502 Bad Gateway |
|
uwsgi: -- unavailable modifier
requested: 0 -- |
|
|
|
502 Bad Gateway |
[error] 59921#59921: *4 connect() to
unix:///var/lib/uwsgi/....sock failed
(111: Connection refused)
while connecting to upstream, client:
x.x.x.x, server: xxxx, request: "GET
/admin HTTP/1.1",
upstream:
"uwsgi://unix:///var/lib/uwsgi/....sock:",
host: "x.x.x.x" |
|
|
uwsgi
plugin "python3" works with Python 3.6 and
not with Python3.8 |
|
502 Bad Gateway |
[error] 2223#0: *131046 connect() to
unix:///var/lib/uwsgi/....sock failed (11:
Resource temporarily unavailable)
while connecting to upstream |
|
|
Resource
temporarily unavailable using uwsgi +
nginx |
- sysctl -w net.core.somaxconn=2048
- uwsgi.ini
|
502
Bad Gateway |
[error] 8211#0: *2082953 upstream
prematurely closed connection while
reading response header from upstream ... |
kernel: Out of memory: Kill process
25020 (uwsgi) score 785 or sacrifice child
...
uwsgi: DAMN ! worker 2 (pid: 25020) died,
killed by signal 9 :( trying respawn ...
uwsgi: Respawned uWSGI worker 2 (new pid:
25117) |
|
|
|
...
|
|
|
[Errno 12] Cannot allocate memory |
Nginx,
unable to fork: Cannot allocate memory
|
- myvassal.ini
- # respawn worker after 100
requests
max_requests=100
- limit-as=1024
- add
swap memory
|
504 Gateway Timeout |
|
|
|
The
Case of the Mysterious AWS ELB 504 Errors |
- Timeout
- nginx.conf
- uwsgi_read_timeout 180s;
- client_header_timeout = 180s;
- python
- @deconstructible
class
MyS3Storage(S3Boto3Storage):
# Possible
solution to 504s on long-timed
upload requests proposed by:
#
https://github.com/jschneier/django-storages/issues/279#issuecomment-522063957
config =
Config(
connect_timeout=4000,
read_timeout=4000,
retries={
'max_attempts': 20
}
)
|
...
(504?) |
|
|
timeout during read(65536) on
wsgi.input |
|
-
Timeout
- myvassal.ini
- [uwsgi]
socket-timeout = 300
|
... 499 (related to the next
one?) |
|
uwsgi: [uwsgi-body-read] Error
reading 57344 bytes. Content-Length:
3691308799
consumed: 2298748928 left: 1392559871
message: Client closed connection
|
email with Django 500 error:
Internal Server Error: ...
UnreadablePostError
at ...
error during read(65536) on wsgi.input
|
|
|
...
(503?, 499?) |
|
SIGPIPE: writing to a closed
pipe/socket/fd (probably the client
disconnected) on request ...
uwsgi_response_writev_headers_and_body_do():
Broken pipe [core/writer.c line 306]
during ...
uwsgi: IOError: write error |
|
|
- nginx.conf
- # when a client closes the
connection then keep the channel to
uwsgi open. Otherwise uwsgi throws
an IOError
uwsgi_ignore_client_abort on;
- myvassal.ini
- ignore-sigpipe = true
ignore-write-errors = true
disable-write-exception = true
|
|
|
|
|
|
|
- Memory leak
[Errno
12] Cannot allocate memory
- django logs:
timeout during read(65536) on wsgi.input
- django logs:
Internal Server Error: ...
UnreadablePostError at ...
error during
read(65536) on wsgi.input
- /var/log/messages:
Sep 14 21:55:32 ip-x-x-x-x kernel: Out of
memory: Kill process 9257 (uwsgi) score 647 or
sacrifice child
/var/log/nginx/error.log: 2020/09/14 21:55:33
[error] 8211#0: *2082953 upstream prematurely
closed connection while reading response header
from upstream ...
- Solució / Solution:
sudo systemctl restart
emperor.uwsgi.service
- add swap memory (EC2 swap)
- /var/log/messages:
SIGPIPE:
writing to a closed pipe/socket/fd (probably the
client disconnected) on request ...
...
uwsgi: IOError: write error
- Gunicorn
|
Settings
|
- Ús / Usage
from django.conf import settings
- Camins relatius / Relative paths
# already set in newest versions
of Django:
import os
PROJECT_PATH =
os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
# Build paths inside the project like this:
os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DATABASES = {
'default': {
...
'NAME':
PROJECT_PATH +
'/sqlite.db',
# Or path to database file if using sqlite3.
'NAME': os.path.join(BASE_DIR, 'sqlite.db'),
#
Or path to database file if using sqlite3.
...
}
}
TEMPLATE_DIRS
= (
PROJECT_PATH + '/templates/'
)
TEMPLATE_DIRS
= [ os.path.join(BASE_DIR, 'templates') ]
- Valors de
settings des de plantilles / Settings values from templates
- Producció / Production
- Diversos fitxers de configuració / Several setting files
- Contrasenyes (per a no
tenir-les al git) / Passwords (to avoid having them in git)
|
Timezone
|
- Timezones
-
|
|
settings
|
|
from
django.utils
import timezone |
|
|
USE_TZ
|
TIME_ZONE
|
now
|
timezone.is_naive(now)
|
timezone.is_aware(now)
|
import datetime
|
now =
datetime.datetime.now()
|
-
|
-
|
datetime.datetime(2015,
2, 19, 16, 40, 13, 962574)
|
True
|
False
|
from
django.utils
import timezone
|
now
=
timezone.now()
|
False
|
-
|
datetime.datetime(2015,
2, 19, 16, 40, 13, 962574)
|
True
|
-
|
datetime.datetime(2015,
2, 19, 15, 40, 13, 962574, tzinfo=<UTC>)
|
False |
True |
- Dates are stored in UTC into the database
- settings.TIME_ZONE is used for user interaction (forms,
list_display, ...)
|
|
- django-cors-headers
- github
- my_project/settings.py
# CORS (django-cors-headers)
CORS_ORIGIN_ALLOW_ALL = True
# based on corsheaders/defaults.py
CORS_ALLOW_HEADERS = (
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
"cache-control",
)
|
Plantilles / Templates
|
- Templates
(1.6)
- The
Django
template language
- Elements
- variable:
{{ original.my_field }}
{{ variable|filter
}}
- convert a string to int:
{{ my_int|slugify }}
- exemple:
{% if my_model.my_int|slugify ==
some_other_int_as_string_from_context %}{%
endif %}
- tag:
{%
tag %}...{% end_tag
%}
- comment:
{# my_comment
#}
{% comment %}...my_comment...{%
endcomment %}
- custom
- Template
inheritance
- my_project/my_app/templates/my_app/view1.html
- my_project/my_app/templates/admin/my_app/my_change_list_template.html
class
TotoAdmin(admin.ModelAdmin)
change_list_template =
'admin/my_app/my_change_list_template.html'
...
- my_project/templates/
- Exemples / Examples
- Personalitza l'aspecte de l'admin
|
Forms
|
|
Seguretat / Security
|
- Contrasenyes / Passwords
- CSRF (Cross Site Request Forgery)
|
Usuaris / Users
|
- Usuaris / Users (used
for authentication, e.g. in Tastypie)
(també disponible a / also available from
http://127.0.0.1:8000/admin/auth/user/ ):
- Usuaris i admin /
Users and admin
- Creació / Creation
# avoid
direct import of User (pylint-django
error)
# from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
usuari =
get_user_model().objects.create_user("usuariprimer",
"usuari@toto.org")
from django.contrib.auth
import get_user_model
class MyModelSerializer(serializers.ModelSerializer):
class Meta(object):
# NOTE: we
cannot use model = settings.AUTH_USER_MODEL because an
error would be raised:
#
AttributeError: 'str' object has no attribute '_meta'
model = get_user_model()
from django.conf import
settings
@receiver(..., sender=settings.AUTH_USER_MODEL)
from django.conf import
settings
class MyModel(models.Model):
myfiled_1 = models.ForeignKey(settings.AUTH_USER_MODEL,
...)
myfield_2 = models.OneToOneField(settings.AUTH_USER_MODEL,
...)
python manage.py shell
>>> from django.contrib.auth.models import
User
>>> user = User.objects.create_user("nom_usuari","adreca@usuari","contrasenya")
>>> user.is_staff = True
>>> user.save()
- Contrasenyes / Passwords
- Test:
curl
--user nom_usuari:contrasenya ...
- Superuser (admin)
- Django
tips:
extending the User model
- Autenticació
d'usuari
/ User authentication
- Autenticació de servidors / Server authentication
- User
authentication
in Django (1.4)
- Authentication: verifies that a user is who he
claims to be
- Authorization: determines what an authenticated
user is allowed to do
-
|
|
Django
|
channels
|
django-rest-framework
authentication
|
|
|
(default)
|
django-allauth
|
|
(default)
|
djangorestframework-jwt |
djangorestframework-simplejwt |
django-rest-auth
/ dj-rest-auth
|
|
|
authentication |
authentication
|
social
authentication
|
|
authentication |
authorization
(permissions)
|
authentication |
|
authentication
|
registration
|
social
authentication
|
|
|
verifies that
a user is who he claims to be
|
|
|
|
|
determines
what an authenticated user is allowed to do
|
|
|
|
|
|
pip install
|
|
|
django-allauth
|
|
|
|
djangorestframework-jwt |
djangorestframework-simplejwt |
django-rest-auth
/ dj-rest-auth
|
settings.py
|
INSTALLED_APPS
|
- 'django.contrib.auth'
- 'django.contrib.contenttypes'
|
- 'django.contrib.sites'
- 'allauth'
- 'allauth.account'
|
- ...
- # also adds admin section "Social
Accounts":
'allauth.socialaccount'
- 'allauth.socialaccount.providers....'
|
|
|
|
|
- # only if i18n is used
'rest_framework_simplejwt',
|
- 'rest_framework'
- 'rest_framework.authtoken'
- 'rest_auth'
- 'dj_rest_auth'
|
- ...
- 'allauth'
- 'allauth.account'
- 'rest_auth.registration'
- 'dj_rest_auth.registration'
|
- ...
- 'allauth.socialaccount'
- 'allauth.socialaccount.providers.facebook'
|
MIDDLEWARE_CLASSES
|
- 'SessionMiddleware'
- 'AuthenticationMiddleware'
- 'SessionAuthenticationMiddleware'
|
|
|
|
|
|
|
|
|
|
|
AUTHENTICATION_BACKENDS
|
- 'django.contrib.auth.backends.ModelBackend'
- username (USERNAME_FIELD) /
password
- 'django.contrib.auth.backends.RemoteUserBackend'
|
- # Needed to login by username in
Django admin, regardless of `allauth`
'django.contrib.auth.backends.ModelBackend',
- # `allauth` specific authentication
methods, such as login by e-mail
'allauth.account.auth_backends.AuthenticationBackend',
|
|
|
|
|
|
|
|