Merge branch 'Kerl/test_user_creation'

This commit is contained in:
Qwann 2017-05-18 18:47:55 +02:00
commit 1ffbac92cb
16 changed files with 464 additions and 195 deletions

View file

@ -0,0 +1,25 @@
# Gestion Événementiel
## Vagrant
### Production-like environment
Our Vagrant setup provides two ways of running GestionEvenementiel:
1. You can run the usual development server with:
python manage.py runserver 0.0.0.0:8000
Please note that we specify the interface `0.0.0.0` to make the server
reachable outside the VM at address `localhost:8000`
2. A second instance, more similar to the production environment, runs with
Daphne and nginx in the VM. It runs permanently by default but is not
reloaded when you update the code. To restart this server, type:
python manage.py collectstatic --noinput
sudo systemctl restart daphne.service worker.service
To query this instance from the host, you have to use the address
`localhost:8080`. It is a good practice to ensure that this instance works
before submitting a merge request (although it might break sometimes).

2
Vagrantfile vendored
View file

@ -10,7 +10,7 @@ Vagrant.configure(2) do |config|
# For a complete reference, please see the online documentation at # For a complete reference, please see the online documentation at
# https://docs.vagrantup.com. # https://docs.vagrantup.com.
config.vm.box = "ubuntu/trusty64" config.vm.box = "debian/contrib-jessie64"
# On associe le port 80 dans la machine virtuelle avec le port 8080 de notre # On associe le port 80 dans la machine virtuelle avec le port 8080 de notre
# ordinateur, et le port 8000 avec le port 8000. # ordinateur, et le port 8000 avec le port 8000.

7
evenementiel/asgi.py Normal file
View file

@ -0,0 +1,7 @@
import os
from channels.asgi import get_channel_layer
if "DJANGO_SETTINGS_MODULE" not in os.environ:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "evenementiel.settings")
channel_layer = get_channel_layer()

2
evenementiel/routing.py Normal file
View file

@ -0,0 +1,2 @@
# Nothing yet
channel_routing = []

1
evenementiel/settings/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
secret.py

View file

@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
"""
Django common settings for GestionÉvénementiel
Everything which is supposed to be identical between the production server and
the local development server should be here.
We also load the secrets in this file.
"""
import os
from . import secret
def import_secret(name):
"""
Shorthand for importing a value from the secret module and raising an
informative exception if a secret is missing.
"""
try:
return getattr(secret, name)
except AttributeError:
raise RuntimeError("Secret missing: {}".format(name))
SECRET_KEY = import_secret("SECRET_KEY")
ADMINS = import_secret("ADMINS")
DBNAME = import_secret("DBNAME")
DBUSER = import_secret("DBUSER")
DBPASSWD = import_secret("DBPASSWD")
REDIS_PASSWD = import_secret("REDIS_PASSWD")
REDIS_DB = import_secret("REDIS_DB")
REDIS_HOST = import_secret("REDIS_HOST")
REDIS_PORT = import_secret("REDIS_PORT")
CREATE_USER_KEY = import_secret("CREATE_USER_KEY")
BASE_DIR = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
INSTALLED_APPS = [
'equipment.apps.EquipmentConfig',
'event.apps.EventConfig',
'users.apps.UsersConfig',
'shared.apps.SharedConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'channels',
'bootstrapform',
'widget_tweaks',
]
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'evenementiel.urls'
STATIC_URL = "/static/"
MEDIA_URL = "/media/"
LOGIN_REDIRECT_URL = 'shared:home'
LOGOUT_REDIRECT_URL = 'shared:home'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'shared.shared.context_processor',
],
},
},
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': DBNAME,
'USER': DBUSER,
'PASSWORD': DBPASSWD,
'PORT': 5432,
'HOST': 'localhost',
}
}
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgi_redis.RedisChannelLayer",
"CONFIG": {
"hosts": [(
"redis://:{passwd}@{host}:{port}/{db}"
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
port=REDIS_PORT, db=REDIS_DB)
)],
},
"ROUTING": "evenementiel.routing.channel_routing",
}
}
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation'
'.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
{'NAME': 'django.contrib.auth.password_validation'
'.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation'
'.NumericPasswordValidator'},
]
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True

View file

@ -0,0 +1,44 @@
"""
Django development settings for GestionÉvénementiel
The settings that are not listed here are imported from .common
"""
from .common import * # NOQA
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DEBUG = True
# Add some debugging tools
INSTALLED_APPS += ["debug_toolbar", "debug_panel"] # NOQA
MIDDLEWARE_CLASSES = (
["debug_panel.middleware.DebugPanelMiddleware"]
+ MIDDLEWARE_CLASSES # NOQA
)
# ---
# Nginx static/media config
# ---
STATIC_ROOT = "/srv/GE/static/"
MEDIA_ROOT = "/srv/GE/media/"
# ---
# Debug tool bar
# ---
def show_toolbar(request):
"""
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
car cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la
machine physique n'est pas forcément connue, et peut difficilement être
mise dans les INTERNAL_IPS.
"""
return DEBUG # True
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
}

View file

@ -0,0 +1,16 @@
SECRET_KEY = 'dummy_key3i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
ADMINS = None
# Postgres
DBNAME = "{{DBNAME}}"
DBUSER = "{{DBUSER}}"
DBPASSWD = "{{DBPASSWD}}"
# Redis
REDIS_PASSWD = "{{REDIS_PASSWD}}"
REDIS_PORT = 6379
REDIS_DB = 0
REDIS_HOST = "127.0.0.1"
# An other secret key used for user creation
CREATE_USER_KEY = "lolilol"

View file

@ -1,162 +0,0 @@
"""
Django settings for evenementiel project.
Generated by 'django-admin startproject' using Django 1.9.9.
For more information on this file, see
https://docs.djangoproject.com/en/1.9/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
"""
import os
from django.core.urlresolvers import reverse_lazy
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '0@=@$0*2x)x=$6qzf*1a(07she(33zr9vi0+=(yd%3i=i9gp+_'
CREATE_USER_KEY = 'lolilol' # Do not use this one on prod !!
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'equipment.apps.EquipmentConfig',
'event.apps.EventConfig',
'users.apps.UsersConfig',
'shared.apps.SharedConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bootstrapform',
'debug_toolbar',
'widget_tweaks',
]
MIDDLEWARE_CLASSES = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'evenementiel.urls'
LOGIN_REDIRECT_URL = reverse_lazy('shared:home')
LOGOUT_REDIRECT_URL = reverse_lazy('shared:home')
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'shared.shared.context_processor',
],
},
},
]
WSGI_APPLICATION = 'evenementiel.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
# # MySQL
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
# PostGreSQL
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.environ['DBNAME'],
'USER': os.environ['DBUSER'],
'PASSWORD': os.environ['DBPASSWD'],
'PORT': 5432,
'HOST': 'localhost',
}
}
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation'
'.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
{'NAME': 'django.contrib.auth.password_validation'
'.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation'
'.NumericPasswordValidator'},
]
# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_URL = '/static/'
def show_toolbar(request):
"""
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
car cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la
machine physique n'est pas forcément connue, et peut difficilement être
mise dans les INTERNAL_IPS.
"""
if not DEBUG:
return False
if request.is_ajax():
return False
return True
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
}

View file

@ -1,45 +1,125 @@
#!/bin/sh #!/bin/bash
# Configuration de la base de données. Le mot de passe est constant car c'est # The credentials of the database. Can be public since they will only be used in
# pour une installation de dév locale qui ne sera accessible que depuis la # the local development environment
# machine virtuelle.
DBUSER="event_gestion" DBUSER="event_gestion"
DBNAME="event_gestion" DBNAME="event_gestion"
DBPASSWD="4KZt3nGPLVeWSvtBZPsd9jdssdJMds78" DBPASSWD="4KZt3nGPLVeWSvtBZPsd9jdssdJMds78"
# Installation de paquets utiles # Not critical either
apt-get update && apt-get install -y python3-pip python3-dev \ REDIS_PASSWD="dummy"
libpq-dev postgresql postgresql-contrib libjpeg-dev
# Setup Database and User # It is used in quite a few places
SETTINGS="evenementiel.settings.dev"
# Fills a "templated file" with the information specified in the variables above
# e.g. every occurrence of {{DBUSER}} in the file will be replaced by the value
# of the variable $DBUSER
function fill_template {
sed "s/{{DBUSER}}/$DBUSER/" -i $1
sed "s/{{DBNAME}}/$DBNAME/" -i $1
sed "s/{{DBPASSWD}}/$DBPASSWD/" -i $1
sed "s/{{REDIS_PASSWD}}/$REDIS_PASSWD/" -i $1
sed "s/{{SETTINGS}}/$SETTINGS/" -i $1
}
# ---
# Installs the dependencies
# ---
# System packages
apt-get update && apt-get upgrade -y
apt-get install -y python3-pip python3-dev python3-venv libpq-dev postgresql \
postgresql-contrib libjpeg-dev nginx redis-server
# Python packages, in a virtual environment
sudo -H -u vagrant python3 -m venv ~vagrant/venv
sudo -H -u vagrant ~vagrant/venv/bin/pip install -U pip wheel
sudo -H -u vagrant ~vagrant/venv/bin/pip install -U -r /vagrant/requirements-devel.txt
# ---
# Setup the production-like environment
# ---
# Database and User
sudo -u postgres createdb $DBNAME sudo -u postgres createdb $DBNAME
sudo -u postgres createuser -SDR $DBUSER sudo -u postgres createuser -SDR $DBUSER
sudo -u postgres psql -c "ALTER USER $DBUSER WITH PASSWORD '$DBPASSWD';" sudo -u postgres psql -c "ALTER USER $DBUSER WITH PASSWORD '$DBPASSWD';"
sudo -u postgres psql -c "ALTER USER $DBUSER CREATEDB;" sudo -u postgres psql -c "ALTER USER $DBUSER CREATEDB;"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $DBNAME TO $DBUSER;" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $DBNAME TO $DBUSER;"
# Mise en place du .bash_profile pour tout configurer lors du `vagrant ssh` # The working directory for Daphne and cie
mkdir -p /srv/GE/{media,static}
chown -R vagrant:vagrant /srv/GE
# Nginx
cp /vagrant/provisioning/nginx.conf /etc/nginx/sites-available/ge.conf
if [ ! -h /etc/nginx/sites-enabled/ge.conf ]
then
# If the configuration file is not activated yet, activates it
ln -s /etc/nginx/sites-available/ge.conf /etc/nginx/sites-enabled/ge.conf
fi
rm -f /etc/nginx/sites-enabled/default # We do not need this
service nginx restart
# Daphne and the worker(s)
for service in {daphne,worker}.service
do
cp /vagrant/provisioning/$service /etc/systemd/system/$service
fill_template /etc/systemd/system/$service
systemctl enable $service
systemctl start $service
done
# Redis
redis-cli CONFIG SET requirepass $REDIS_PASSWD
if [ ! $? ]
then
# In case the requirepass command failed, checks that it was because the
# password was already set *to the right value*.
redis-cli AUTH $REDIS_PASSWD
fi
redis-cli -a $REDIS_PASSWD CONFIG REWRITE
# ---
# Prepare Django
# ---
cd /vagrant
# Setup the secrets
sudo -H -u vagrant cp evenementiel/settings/secret_example.py \
evenementiel/settings/secret.py
fill_template evenementiel/settings/secret.py
# Run the usual django admin commands
function venv_python {
sudo -H -u vagrant DJANGO_SETTINGS_MODULE=$SETTINGS \
~vagrant/venv/bin/python \
$@
}
venv_python manage.py collectstatic --noinput
venv_python manage.py migrate
# ---
# Setup a friendly environment for the user
# ---
cat > ~vagrant/.bash_profile <<EOF cat > ~vagrant/.bash_profile <<EOF
# On utilise la version de prod de qwann.fr # Use the .bashrc file provided by Debian
export DJANGO_SETTINGS_MODULE='evenementiel.settings_dev' source .bashrc
# Identifiants MySQL # Use the development settings
export DBUSER="$DBUSER" export DJANGO_SETTINGS_MODULE="$SETTINGS"
export DBNAME="$DBNAME"
export DBPASSWD="$DBPASSWD"
# On va dans /vagrant où se trouve le code # Move to the code's folder
cd /vagrant cd /vagrant
# Activate the virtualenv
source ~vagrant/venv/bin/activate
EOF EOF
chown vagrant: ~vagrant/.bash_profile chown vagrant: ~vagrant/.bashrc
# On va dans /vagrant où se trouve le code
cd /vagrant
# Installation des dépendances python
sudo -H pip3 install -U pip
sudo -H -u vagrant pip3 install --user -r requirements.txt
sudo -H -u vagrant pip3 install --user -r requirements-devel.txt
# Préparation de Django
sudo -H -u vagrant DJANGO_SETTINGS_MODULE='evenementiel.settings_dev' DBUSER=$DBUSER DBNAME=$DBNAME DBPASSWD=$DBPASSWD python3 manage.py migrate

View file

@ -0,0 +1,16 @@
Description="Gestion Événementiel - Daphne"
After=syslog.target
After=network.target
[Service]
Type=simple
User=vagrant
Group=vagrant
TimeoutSec=300
WorkingDirectory=/vagrant
Environment="DJANGO_SETTINGS_MODULE={{SETTINGS}}"
ExecStart=/home/vagrant/venv/bin/daphne -u /srv/GE/GE.sock \
evenementiel.asgi:channel_layer
[Install]
WantedBy=multi-user.target

40
provisioning/nginx.conf Normal file
View file

@ -0,0 +1,40 @@
upstream GE {
# Daphne listens on a unix socket
server unix:/srv/GE/GE.sock;
}
server {
listen 80;
server_name localhost;
# All the trafic is routed to Daphne
location ~ ^/ {
# A copy-paste of what we have in production
proxy_set_header Host $http_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-Proto $scheme;
proxy_set_header X-SSL-Client-Serial $ssl_client_serial;
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
proxy_set_header X-SSL-Client-S-DN $ssl_client_s_dn;
# Reverse-proxy
proxy_pass http://GE;
}
# Static files
location ~ ^/static/ {
root /srv/GE/static/;
access_log off;
add_header Cache-Control "public";
expires 7d;
}
# Uploaded media
location ~ ^/media/ {
root /srv/GE/static/;
access_log off;
add_header Cache-Control "public";
expires 7d;
}
}

View file

@ -0,0 +1,16 @@
[Unit]
Description="Gestion Événementiel - Worker"
After=syslog.target
After=network.target
[Service]
Type=simple
User=vagrant
Group=vagrant
TimeoutSec=300
WorkingDirectory=/vagrant
Environment="DJANGO_SETTINGS_MODULE={{SETTINGS}}"
ExecStart=/home/vagrant/venv/bin/python manage.py runworker
[Install]
WantedBy=multi-user.target

View file

@ -1,2 +1,4 @@
-r requirements.txt
django-debug-toolbar django-debug-toolbar
django-debug-panel
ipython ipython

View file

@ -1,5 +1,10 @@
Django==1.11.* Django==1.11.*
Pillow==3.3.0 psycopg2
psycopg2==2.6.2 asgi-redis
Pillow
channels
django-bootstrap-form==3.2.1 django-bootstrap-form==3.2.1
django-widget-tweaks django-widget-tweaks
# Production specific
daphne

View file

@ -1,3 +1,34 @@
from django.test import TestCase from django.test import TestCase, Client
from django.conf import settings
from django.contrib.auth.models import User
# Create your tests here.
class TestUserCreation(TestCase):
def test_create_view(self):
"""Create a user using the user creation form"""
user_data = {
"username": "MrsFoobar",
"first_name": "Baz",
"last_name": "Foobar",
"email": "baz@foobar.net",
}
data = user_data.copy()
data["password1"] = "4zwY5jdI"
data["password2"] = "4zwY5jdI"
data["key"] = settings.CREATE_USER_KEY
client = Client()
resp = client.post("/user/create/", data)
# The user redirection means successful form validation
self.assertRedirects(resp, "/")
# The user should now exist
user = (
User.objects
.filter(username=data["username"])
.values(*user_data.keys())
.get()
)
self.assertEqual(user_data, user)