start a Publik Django application (#64765)
This commit is contained in:
parent
05fb86a97f
commit
dbdf44777f
|
@ -11,3 +11,6 @@ lingo.egg-info/
|
||||||
.cache
|
.cache
|
||||||
.coverage
|
.coverage
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
junit*xml
|
||||||
|
pylint.out
|
||||||
|
*.swp
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
@Library('eo-jenkins-lib@main') import eo.Utils
|
||||||
|
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
options {
|
||||||
|
disableConcurrentBuilds()
|
||||||
|
timeout(time: 20, unit: 'MINUTES')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Unit Tests') {
|
||||||
|
steps {
|
||||||
|
sh 'tox -rv'
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
script {
|
||||||
|
utils = new Utils()
|
||||||
|
utils.publish_coverage('coverage.xml')
|
||||||
|
utils.publish_coverage_native('index.html')
|
||||||
|
utils.publish_pylint('pylint.out')
|
||||||
|
}
|
||||||
|
mergeJunitResults()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Packaging') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
if (env.JOB_NAME == 'lingo' && env.GIT_BRANCH == 'origin/main') {
|
||||||
|
sh 'sudo -H -u eobuilder /usr/local/bin/eobuilder -d buster,bullseye lingo'
|
||||||
|
} else if (env.GIT_BRANCH.startsWith('hotfix/')) {
|
||||||
|
sh "sudo -H -u eobuilder /usr/local/bin/eobuilder -d buster,bullseye --branch ${env.GIT_BRANCH} --hotfix lingo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
script {
|
||||||
|
utils = new Utils()
|
||||||
|
utils.mail_notify(currentBuild, env, 'ci+jenkins-lingo@entrouvert.org')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
success {
|
||||||
|
cleanWs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
# locales
|
||||||
|
recursive-include lingo/locale *.po *.mo
|
||||||
|
|
||||||
|
# static
|
||||||
|
recursive-include lingo/static *.gif *.png *.css *.js
|
||||||
|
|
||||||
|
# templates
|
||||||
|
recursive-include lingo/templates *.html
|
||||||
|
|
||||||
|
include COPYING README
|
||||||
|
include MANIFEST.in
|
||||||
|
include VERSION
|
|
@ -0,0 +1,5 @@
|
||||||
|
lingo (0.0-1) unstable; urgency=low
|
||||||
|
|
||||||
|
* Initial release
|
||||||
|
|
||||||
|
-- Thomas NOËL <tnoel@entrouvert.com> Mon, 2 May 2022 23:22:44 +0200
|
|
@ -0,0 +1,33 @@
|
||||||
|
Source: lingo
|
||||||
|
Maintainer: Thomas NOËL <tnoel@entrouvert.com>
|
||||||
|
Section: python
|
||||||
|
Priority: optional
|
||||||
|
Build-Depends: python3-setuptools, python3-all, python3-django, debhelper-compat (= 12), dh-python
|
||||||
|
Standards-Version: 3.9.6
|
||||||
|
|
||||||
|
Package: python3-lingo
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${misc:Depends}, ${python3:Depends},
|
||||||
|
python3-distutils,
|
||||||
|
python3-django,
|
||||||
|
python3-djangorestframework,
|
||||||
|
python3-gadjo,
|
||||||
|
python3-requests,
|
||||||
|
python3-eopayment
|
||||||
|
Recommends: python3-django-mellon
|
||||||
|
Description: Payment and Billing System (Python module)
|
||||||
|
|
||||||
|
Package: lingo
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${misc:Depends},
|
||||||
|
python3-lingo (= ${binary:Version}),
|
||||||
|
python3-hobo,
|
||||||
|
python3-django-tenant-schemas,
|
||||||
|
python3-psycopg2,
|
||||||
|
python3-django-mellon,
|
||||||
|
uwsgi,
|
||||||
|
uwsgi-plugin-python3,
|
||||||
|
python3-uwsgidecorators
|
||||||
|
Recommends: nginx
|
||||||
|
Suggests: postgresql
|
||||||
|
Description: Payment and Billing System
|
|
@ -0,0 +1,18 @@
|
||||||
|
# This file is sourced by "execfile" from lingo.settings
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
PROJECT_NAME = 'lingo'
|
||||||
|
|
||||||
|
#
|
||||||
|
# hobotization (multitenant)
|
||||||
|
#
|
||||||
|
exec(open('/usr/lib/hobo/debian_config_common.py').read())
|
||||||
|
|
||||||
|
#
|
||||||
|
# local settings
|
||||||
|
#
|
||||||
|
exec(open(os.path.join(ETC_DIR, 'settings.py')).read())
|
||||||
|
|
||||||
|
# run additional settings snippets
|
||||||
|
exec(open('/usr/lib/hobo/debian_config_settings_d.py').read())
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
NAME=lingo
|
||||||
|
MANAGE=/usr/lib/$NAME/manage.py
|
||||||
|
|
||||||
|
# load Debian default configuration
|
||||||
|
export LINGO_SETTINGS_FILE=/usr/lib/$NAME/debian_config.py
|
||||||
|
|
||||||
|
# check user
|
||||||
|
if test x$1 = x"--forceuser"
|
||||||
|
then
|
||||||
|
shift
|
||||||
|
elif test $(id -un) != "$NAME"
|
||||||
|
then
|
||||||
|
echo "error: must use $0 with user ${NAME}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test $# -eq 0
|
||||||
|
then
|
||||||
|
python3 ${MANAGE} help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 ${MANAGE} "$@"
|
|
@ -0,0 +1,7 @@
|
||||||
|
/etc/lingo
|
||||||
|
/usr/share/lingo/themes
|
||||||
|
/usr/lib/lingo
|
||||||
|
/var/lib/lingo/collectstatic
|
||||||
|
/var/lib/lingo/tenants
|
||||||
|
/var/log/lingo
|
||||||
|
/var/lib/lingo/spooler
|
|
@ -0,0 +1,3 @@
|
||||||
|
COPYING
|
||||||
|
README
|
||||||
|
debian/nginx-example.conf
|
|
@ -0,0 +1,168 @@
|
||||||
|
#!/bin/sh
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: lingo
|
||||||
|
# Required-Start: $network $local_fs $remote_fs $syslog
|
||||||
|
# Required-Stop: $network $local_fs $remote_fs $syslog
|
||||||
|
# Should-start: postgresql
|
||||||
|
# Should-stop: postgresql
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: Billing and Payment System
|
||||||
|
# Description: Billing and Payment System
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
# Author: Entr'ouvert <info@entrouvert.com>
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||||
|
DESC="Billing and Payment System"
|
||||||
|
NAME=lingo
|
||||||
|
DAEMON=/usr/bin/uwsgi
|
||||||
|
RUN_DIR=/run/$NAME
|
||||||
|
PIDFILE=$RUN_DIR/$NAME.pid
|
||||||
|
LOG_DIR=/var/log/$NAME
|
||||||
|
SCRIPTNAME=/etc/init.d/$NAME
|
||||||
|
BIND=unix:$RUN_DIR/$NAME.sock
|
||||||
|
|
||||||
|
LINGO_SETTINGS_FILE=/usr/lib/$NAME/debian_config.py
|
||||||
|
MANAGE_SCRIPT="/usr/bin/$NAME-manage"
|
||||||
|
|
||||||
|
USER=$NAME
|
||||||
|
GROUP=$NAME
|
||||||
|
|
||||||
|
# Exit if the package is not installed
|
||||||
|
[ -x $MANAGE_SCRIPT ] || exit 0
|
||||||
|
|
||||||
|
# Read configuration variable file if it is present
|
||||||
|
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||||
|
|
||||||
|
DAEMON_ARGS=${DAEMON_ARGS:-"--pidfile=$PIDFILE
|
||||||
|
--uid $USER --gid $GROUP
|
||||||
|
--ini /etc/$NAME/uwsgi.ini
|
||||||
|
--spooler /var/lib/$NAME/spooler/
|
||||||
|
--daemonize /var/log/uwsgi.$NAME.log"}
|
||||||
|
|
||||||
|
# Load the VERBOSE setting and other rcS variables
|
||||||
|
. /lib/init/vars.sh
|
||||||
|
|
||||||
|
# Define LSB log_* functions.
|
||||||
|
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
# Create /run directory
|
||||||
|
if [ ! -d $RUN_DIR ]; then
|
||||||
|
install -d -m 755 -o $USER -g $GROUP $RUN_DIR
|
||||||
|
fi
|
||||||
|
|
||||||
|
# environment for wsgi
|
||||||
|
export LINGO_SETTINGS_FILE
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function that starts the daemon/service
|
||||||
|
#
|
||||||
|
do_start()
|
||||||
|
{
|
||||||
|
# Return
|
||||||
|
# 0 if daemon has been started
|
||||||
|
# 1 if daemon was already running
|
||||||
|
# 2 if daemon could not be started
|
||||||
|
start-stop-daemon --start --quiet --user $USER --exec $DAEMON -- \
|
||||||
|
$DAEMON_ARGS \
|
||||||
|
|| return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function that stops the daemon/service
|
||||||
|
#
|
||||||
|
do_stop()
|
||||||
|
{
|
||||||
|
# Return
|
||||||
|
# 0 if daemon has been stopped
|
||||||
|
# 1 if daemon was already stopped
|
||||||
|
# 2 if daemon could not be stopped
|
||||||
|
# other if a failure occurred
|
||||||
|
$DAEMON --stop $PIDFILE
|
||||||
|
rm -f $PIDFILE
|
||||||
|
return 0 # hopefully
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function that sends a SIGHUP to the daemon/service
|
||||||
|
#
|
||||||
|
do_reload() {
|
||||||
|
$DAEMON --reload $PIDFILE
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
do_migrate() {
|
||||||
|
log_action_msg "Applying migrations (migrate_schemas).."
|
||||||
|
su $USER -s /bin/sh -p -c "$MANAGE_SCRIPT migrate_schemas --noinput"
|
||||||
|
log_action_msg "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_collectstatic() {
|
||||||
|
log_action_msg "Collect static files (collectstatic).."
|
||||||
|
su $USER -s /bin/sh -p -c "$MANAGE_SCRIPT collectstatic --noinput"
|
||||||
|
log_action_msg "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
log_daemon_msg "Starting $DESC " "$NAME"
|
||||||
|
do_migrate
|
||||||
|
do_collectstatic
|
||||||
|
do_start
|
||||||
|
case "$?" in
|
||||||
|
0|1) log_end_msg 0 ;;
|
||||||
|
2) log_end_msg 1 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
log_daemon_msg "Stopping $DESC" "$NAME"
|
||||||
|
do_stop
|
||||||
|
case "$?" in
|
||||||
|
0|1) log_end_msg 0 ;;
|
||||||
|
2) log_end_msg 1 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
status_of_proc -p $PIDFILE "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||||
|
;;
|
||||||
|
reload|force-reload)
|
||||||
|
#
|
||||||
|
# If do_reload() is not implemented then leave this commented out
|
||||||
|
# and leave 'force-reload' as an alias for 'restart'.
|
||||||
|
#
|
||||||
|
log_daemon_msg "Reloading $DESC" "$NAME"
|
||||||
|
do_reload
|
||||||
|
log_end_msg $?
|
||||||
|
;;
|
||||||
|
restart|force-reload)
|
||||||
|
#
|
||||||
|
# If the "reload" option is implemented then remove the
|
||||||
|
# 'force-reload' alias
|
||||||
|
#
|
||||||
|
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||||
|
do_stop
|
||||||
|
case "$?" in
|
||||||
|
0|1)
|
||||||
|
do_migrate
|
||||||
|
do_collectstatic
|
||||||
|
do_start
|
||||||
|
case "$?" in
|
||||||
|
0) log_end_msg 0 ;;
|
||||||
|
1) log_end_msg 1 ;; # Old process is still running
|
||||||
|
*) log_end_msg 1 ;; # Failed to start
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Failed to stop
|
||||||
|
log_end_msg 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload|force-reload}" >&2
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
esac
|
|
@ -0,0 +1,4 @@
|
||||||
|
debian/lingo-manage /usr/bin
|
||||||
|
debian/settings.py /etc/lingo
|
||||||
|
debian/uwsgi.ini /etc/lingo
|
||||||
|
debian/debian_config.py /usr/lib/lingo
|
|
@ -0,0 +1,50 @@
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
NAME="lingo"
|
||||||
|
USER=$NAME
|
||||||
|
GROUP=$NAME
|
||||||
|
CONFIG_DIR="/etc/$NAME"
|
||||||
|
MANAGE_SCRIPT="/usr/bin/$NAME-manage"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
configure)
|
||||||
|
|
||||||
|
# make sure the administrative user exists
|
||||||
|
if ! getent passwd $USER >/dev/null; then
|
||||||
|
adduser --disabled-password --quiet --system \
|
||||||
|
--no-create-home --home /var/lib/$NAME \
|
||||||
|
--gecos "$NAME user" --group $USER
|
||||||
|
fi
|
||||||
|
# ensure dirs ownership
|
||||||
|
chown $USER:$GROUP /var/log/$NAME
|
||||||
|
chown $USER:$GROUP /var/lib/$NAME/collectstatic
|
||||||
|
chown $USER:$GROUP /var/lib/$NAME/tenants
|
||||||
|
chown $USER:$GROUP /var/lib/$NAME/spooler
|
||||||
|
# create a secret file
|
||||||
|
SECRET_FILE=$CONFIG_DIR/secret
|
||||||
|
if [ ! -f $SECRET_FILE ]; then
|
||||||
|
echo -n "Generating Django secret..." >&2
|
||||||
|
cat /dev/urandom | tr -dc [:alnum:]-_\!\%\^:\; | head -c70 > $SECRET_FILE
|
||||||
|
chown root:$GROUP $SECRET_FILE
|
||||||
|
chmod 0440 $SECRET_FILE
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
triggered)
|
||||||
|
su -s /bin/sh -c "$MANAGE_SCRIPT hobo_deploy --redeploy" $USER
|
||||||
|
;;
|
||||||
|
|
||||||
|
abort-upgrade|abort-remove|abort-deconfigure)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "postinst called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,26 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Lingo
|
||||||
|
After=network.target syslog.target postgresql.service
|
||||||
|
Wants=postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=LINGO_SETTINGS_FILE=/usr/lib/%p/debian_config.py
|
||||||
|
Environment=LANG=C.UTF-8
|
||||||
|
User=%p
|
||||||
|
Group=%p
|
||||||
|
ExecStartPre=/usr/bin/lingo-manage migrate_schemas --noinput --verbosity 1
|
||||||
|
ExecStartPre=/usr/bin/lingo-manage collectstatic --noinput --link
|
||||||
|
ExecStartPre=/bin/mkdir -p /var/lib/lingo/spooler/%m/
|
||||||
|
ExecStart=/usr/bin/uwsgi --ini /etc/%p/uwsgi.ini --spooler /var/lib/lingo/spooler/%m/
|
||||||
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
|
KillSignal=SIGQUIT
|
||||||
|
TimeoutStartSec=0
|
||||||
|
PrivateTmp=true
|
||||||
|
Restart=on-failure
|
||||||
|
RuntimeDirectory=lingo
|
||||||
|
Type=notify
|
||||||
|
StandardError=syslog
|
||||||
|
NotifyAccess=all
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -0,0 +1 @@
|
||||||
|
interest-noawait hobo-redeploy
|
|
@ -0,0 +1,44 @@
|
||||||
|
server {
|
||||||
|
listen 443;
|
||||||
|
server_name *-lingo.example.org;
|
||||||
|
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
|
||||||
|
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/lingo.example.org-access.log combined;
|
||||||
|
error_log /var/log/nginx/lingo.example.org-error.log;
|
||||||
|
|
||||||
|
location ~ ^/static/(.+)$ {
|
||||||
|
root /;
|
||||||
|
try_files /var/lib/lingo/tenants/$host/static/$1
|
||||||
|
/var/lib/lingo/tenants/$host/theme/static/$1
|
||||||
|
/var/lib/lingo/collectstatic/$1
|
||||||
|
=404;
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/media/(.+)$ {
|
||||||
|
alias /var/lib/lingo/tenants/$host/media/$1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://unix:/var/run/lingo/lingo.sock;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Forwarded-SSL on;
|
||||||
|
proxy_set_header X-Forwarded-Protocol ssl;
|
||||||
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name *-lingo.example.org;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/lingo.example.org-access.log combined;
|
||||||
|
error_log /var/log/nginx/lingo.example.org-error.log;
|
||||||
|
|
||||||
|
return 302 https://$host$request_uri;
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
eopayment python3-eopayment
|
||||||
|
gadjo python3-gadjo
|
|
@ -0,0 +1 @@
|
||||||
|
/usr/lib/lingo
|
|
@ -0,0 +1,2 @@
|
||||||
|
COPYING
|
||||||
|
README
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
# -*- makefile -*-
|
||||||
|
|
||||||
|
export PYBUILD_NAME=lingo
|
||||||
|
export PYBUILD_DISABLE=test
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@ --with python3 --buildsystem=pybuild
|
||||||
|
|
||||||
|
override_dh_install:
|
||||||
|
dh_install
|
||||||
|
mv $(CURDIR)/debian/python3-lingo/usr/bin/manage.py $(CURDIR)/debian/lingo/usr/lib/lingo/manage.py
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Configuration for lingo.
|
||||||
|
|
||||||
|
# Override with /etc/lingo/settings.d/ files
|
||||||
|
|
||||||
|
# Lingo is a Django application: for the full list of settings and their
|
||||||
|
# values, see https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||||
|
# For more information on settings see
|
||||||
|
# https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||||
|
|
||||||
|
# WARNING! Quick-start development settings unsuitable for production!
|
||||||
|
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# This file is sourced by "exec(open(...).read())" from
|
||||||
|
# /usr/lib/lingo/debian_config.py
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
# ALLOWED_HOSTS must be correct in production!
|
||||||
|
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||||
|
ALLOWED_HOSTS = [
|
||||||
|
'*',
|
||||||
|
]
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'fr-fr'
|
||||||
|
TIME_ZONE = 'Europe/Paris'
|
|
@ -0,0 +1 @@
|
||||||
|
3.0 (quilt)
|
|
@ -0,0 +1,51 @@
|
||||||
|
[uwsgi]
|
||||||
|
auto-procname = true
|
||||||
|
procname-prefix-spaced = lingo
|
||||||
|
strict = true
|
||||||
|
|
||||||
|
plugin = python3
|
||||||
|
single-interpreter = true
|
||||||
|
module = lingo.wsgi:application
|
||||||
|
need-app = true
|
||||||
|
|
||||||
|
http-socket = /run/lingo/lingo.sock
|
||||||
|
chmod-socket = 666
|
||||||
|
vacuum = true
|
||||||
|
|
||||||
|
spooler-processes = 3
|
||||||
|
spooler-python-import = lingo.utils.spooler
|
||||||
|
spooler-python-import = hobo.provisionning.spooler
|
||||||
|
spooler-max-tasks = 20
|
||||||
|
|
||||||
|
master = true
|
||||||
|
enable-threads = true
|
||||||
|
harakiri = 120
|
||||||
|
|
||||||
|
processes = 500
|
||||||
|
|
||||||
|
plugin = cheaper_busyness
|
||||||
|
cheaper-algo = busyness
|
||||||
|
cheaper = 5
|
||||||
|
cheaper-initial = 10
|
||||||
|
cheaper-overload = 5
|
||||||
|
cheaper-step = 10
|
||||||
|
cheaper-busyness-multiplier = 30
|
||||||
|
cheaper-busyness-min = 20
|
||||||
|
cheaper-busyness-max = 70
|
||||||
|
cheaper-busyness-backlog-alert = 16
|
||||||
|
cheaper-busyness-backlog-step = 2
|
||||||
|
|
||||||
|
max-requests = 500
|
||||||
|
max-worker-lifetime = 7200
|
||||||
|
|
||||||
|
buffer-size = 32768
|
||||||
|
|
||||||
|
py-tracebacker = /run/lingo/py-tracebacker.sock.
|
||||||
|
stats = /run/lingo/stats.sock
|
||||||
|
|
||||||
|
ignore-sigpipe = true
|
||||||
|
disable-write-exception = true
|
||||||
|
|
||||||
|
if-file = /etc/lingo/uwsgi-local.ini
|
||||||
|
include = /etc/lingo/uwsgi-local.ini
|
||||||
|
endif =
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Get venv site-packages path
|
||||||
|
DSTDIR=`python3 -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())'`
|
||||||
|
|
||||||
|
# Get not venv site-packages path
|
||||||
|
# Remove first path (assuming that is the venv path)
|
||||||
|
NONPATH=`echo $PATH | sed 's/^[^:]*://'`
|
||||||
|
SRCDIR=`PATH=$NONPATH python3 -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())'`
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm -f $DSTDIR/lasso.*
|
||||||
|
rm -f $DSTDIR/_lasso.*
|
||||||
|
|
||||||
|
# Link
|
||||||
|
ln -sv /usr/lib/python3/dist-packages/lasso.py $DSTDIR/
|
||||||
|
for SOFILE in /usr/lib/python3/dist-packages/_lasso.cpython-*.so
|
||||||
|
do
|
||||||
|
ln -sv $SOFILE $DSTDIR/
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,24 @@
|
||||||
|
# lingo - payment and billing system, french translation
|
||||||
|
# Copyright (C) 2022 Entr'ouvert
|
||||||
|
# This file is distributed under the same license as the lingo package.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: lingo 0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2022-05-02 22:19+0000\n"
|
||||||
|
"PO-Revision-Date: 2022-05-02 22:13+0000\n"
|
||||||
|
"Last-Translator: Thomas NOËL <tnoel@entrouvert.com>\n"
|
||||||
|
"Language: French\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: manager/views.py templates/lingo/manager_homepage.html
|
||||||
|
msgid "Payments"
|
||||||
|
msgstr "Paiements"
|
||||||
|
|
||||||
|
#: templates/registration/login.html
|
||||||
|
msgid "Log in"
|
||||||
|
msgstr "S’identifier"
|
|
@ -0,0 +1,24 @@
|
||||||
|
# lingo - billing and payment system
|
||||||
|
# Copyright (C) 2022 Entr'ouvert
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^$', views.homepage, name='manager-homepage'),
|
||||||
|
url(r'^menu.json$', views.menu_json),
|
||||||
|
]
|
|
@ -0,0 +1,50 @@
|
||||||
|
# lingo - payment and billing system
|
||||||
|
# Copyright (C) 2022 Entr'ouvert
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
|
class HomepageView(TemplateView):
|
||||||
|
template_name = 'lingo/manager_homepage.html'
|
||||||
|
|
||||||
|
|
||||||
|
homepage = HomepageView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
def menu_json(request):
|
||||||
|
label = _('Payments')
|
||||||
|
json_str = json.dumps(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'label': label,
|
||||||
|
'slug': 'lingo',
|
||||||
|
'url': request.build_absolute_uri(reverse('manage-homepage')),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
content_type = 'application/json'
|
||||||
|
for variable in ('jsonpCallback', 'callback'):
|
||||||
|
if variable in request.GET:
|
||||||
|
json_str = '%s(%s);' % (request.GET[variable], json_str)
|
||||||
|
content_type = 'application/javascript'
|
||||||
|
break
|
||||||
|
response = HttpResponse(content_type=content_type)
|
||||||
|
response.write(json_str)
|
||||||
|
return response
|
|
@ -0,0 +1,190 @@
|
||||||
|
# lingo - payment and bill system
|
||||||
|
# Copyright (C) 2022 Entr'ouvert
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Django settings file; it loads the default settings, and local settings
|
||||||
|
(from a local_settings.py file, or a configuration file set in the
|
||||||
|
LINGO_SETTINGS_FILE environment variable).
|
||||||
|
|
||||||
|
The local settings file should exist, at least to set a suitable SECRET_KEY,
|
||||||
|
and to disable DEBUG mode in production.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import global_settings
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'r^(w+o4*txe1=t+0w*w3*9%idij!yeq1#axpsi4%5*u#3u&)1t'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.humanize',
|
||||||
|
'eopayment',
|
||||||
|
'gadjo',
|
||||||
|
)
|
||||||
|
|
||||||
|
MIDDLEWARE = (
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Serve xstatic files, required for gadjo
|
||||||
|
STATICFILES_FINDERS = list(global_settings.STATICFILES_FINDERS) + ['gadjo.finders.XStaticFinder']
|
||||||
|
|
||||||
|
# Templates
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [
|
||||||
|
os.path.join(BASE_DIR, 'lingo', 'templates'),
|
||||||
|
],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.i18n',
|
||||||
|
'django.template.context_processors.media',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.template.context_processors.static',
|
||||||
|
'django.template.context_processors.tz',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
'builtins': [
|
||||||
|
'django.contrib.humanize.templatetags.humanize',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'lingo.urls'
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'lingo.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'fr-fr'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
LOCALE_PATHS = (os.path.join(BASE_DIR, 'lingo', 'locale'),)
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
|
# mode for newly updated files
|
||||||
|
FILE_UPLOAD_PERMISSIONS = 0o644
|
||||||
|
|
||||||
|
# extra variables for templates
|
||||||
|
TEMPLATE_VARS = {}
|
||||||
|
|
||||||
|
# Authentication settings
|
||||||
|
try:
|
||||||
|
import mellon
|
||||||
|
except ImportError:
|
||||||
|
mellon = None
|
||||||
|
|
||||||
|
if mellon is not None:
|
||||||
|
INSTALLED_APPS += ('mellon',)
|
||||||
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
'mellon.backends.SAMLBackend',
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
)
|
||||||
|
|
||||||
|
LOGIN_URL = '/login/'
|
||||||
|
LOGIN_REDIRECT_URL = '/'
|
||||||
|
LOGOUT_URL = '/logout/'
|
||||||
|
|
||||||
|
MELLON_ATTRIBUTE_MAPPING = {
|
||||||
|
'email': '{attributes[email][0]}',
|
||||||
|
'first_name': '{attributes[first_name][0]}',
|
||||||
|
'last_name': '{attributes[last_name][0]}',
|
||||||
|
}
|
||||||
|
|
||||||
|
MELLON_SUPERUSER_MAPPING = {
|
||||||
|
'is_superuser': 'true',
|
||||||
|
}
|
||||||
|
|
||||||
|
MELLON_USERNAME_TEMPLATE = '{attributes[name_id_content]}'
|
||||||
|
|
||||||
|
MELLON_IDENTITY_PROVIDERS = []
|
||||||
|
|
||||||
|
|
||||||
|
# default site
|
||||||
|
SITE_BASE_URL = 'http://localhost'
|
||||||
|
|
||||||
|
# known services
|
||||||
|
KNOWN_SERVICES = {}
|
||||||
|
|
||||||
|
|
||||||
|
def debug_show_toolbar(request):
|
||||||
|
from debug_toolbar.middleware import show_toolbar as dt_show_toolbar # pylint: disable=import-error
|
||||||
|
|
||||||
|
return dt_show_toolbar(request) and not request.path.startswith('/__skeleton__/')
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {'SHOW_TOOLBAR_CALLBACK': debug_show_toolbar}
|
||||||
|
|
||||||
|
|
||||||
|
local_settings_file = os.environ.get(
|
||||||
|
'LINGO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
|
||||||
|
)
|
||||||
|
if os.path.exists(local_settings_file):
|
||||||
|
with open(local_settings_file) as fd:
|
||||||
|
exec(fd.read())
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "gadjo/base.html" %}
|
||||||
|
{% load i18n staticfiles gadjo %}
|
||||||
|
{% block page-title %}Lingo{% endblock %}
|
||||||
|
{% block site-title %}Lingo{% endblock %}
|
||||||
|
|
||||||
|
{% block logout-url %}{% url "auth_logout" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1 @@
|
||||||
|
{% extends "lingo/base.html" %}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{% extends "lingo/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block appbar %}
|
||||||
|
<h2>{% trans 'Payments' %}</h2>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends "lingo/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button>{% trans 'Log in' %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,53 @@
|
||||||
|
# lingo - payment and billing system
|
||||||
|
# Copyright (C) 2022 Entr'ouvert
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
|
|
||||||
|
from .manager.urls import urlpatterns as lingo_manager_urls
|
||||||
|
from .urls_utils import decorated_includes, manager_required
|
||||||
|
from .views import homepage, login, logout
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^$', homepage, name='homepage'),
|
||||||
|
url(r'^manage/', decorated_includes(manager_required, include(lingo_manager_urls))),
|
||||||
|
url(r'^login/$', login, name='auth_login'),
|
||||||
|
url(r'^logout/$', logout, name='auth_logout'),
|
||||||
|
]
|
||||||
|
|
||||||
|
if 'mellon' in settings.INSTALLED_APPS:
|
||||||
|
urlpatterns.append(
|
||||||
|
url(
|
||||||
|
r'^accounts/mellon/',
|
||||||
|
include('mellon.urls'),
|
||||||
|
kwargs={
|
||||||
|
'template_base': 'lingo/base.html',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# static and media files
|
||||||
|
urlpatterns += staticfiles_urlpatterns()
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
|
if settings.DEBUG and 'debug_toolbar' in settings.INSTALLED_APPS:
|
||||||
|
import debug_toolbar # pylint: disable=import-error
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||||
|
] + urlpatterns
|
|
@ -0,0 +1,65 @@
|
||||||
|
# lingo - payment and billing system
|
||||||
|
# Copyright (C) 2022 Entr'ouvert
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Decorating URL includes, <https://djangosnippets.org/snippets/2532/>
|
||||||
|
|
||||||
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.urls.resolvers import URLPattern, URLResolver
|
||||||
|
|
||||||
|
|
||||||
|
class DecoratedURLPattern(URLPattern):
|
||||||
|
def resolve(self, *args, **kwargs):
|
||||||
|
result = super().resolve(*args, **kwargs)
|
||||||
|
if result:
|
||||||
|
result.func = self._decorate_with(result.func)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class DecoratedURLResolver(URLResolver):
|
||||||
|
def resolve(self, *args, **kwargs):
|
||||||
|
result = super().resolve(*args, **kwargs)
|
||||||
|
if result:
|
||||||
|
result.func = self._decorate_with(result.func)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def decorated_includes(func, includes, *args, **kwargs):
|
||||||
|
urlconf_module, app_name, namespace = includes
|
||||||
|
|
||||||
|
for item in urlconf_module:
|
||||||
|
if isinstance(item, URLResolver):
|
||||||
|
item.__class__ = DecoratedURLResolver
|
||||||
|
else:
|
||||||
|
item.__class__ = DecoratedURLPattern
|
||||||
|
item._decorate_with = func
|
||||||
|
|
||||||
|
return urlconf_module, app_name, namespace
|
||||||
|
|
||||||
|
|
||||||
|
def manager_required(function=None, login_url=None):
|
||||||
|
def check_manager(user):
|
||||||
|
if user and user.is_staff:
|
||||||
|
return True
|
||||||
|
if user and not user.is_anonymous:
|
||||||
|
raise PermissionDenied()
|
||||||
|
# As the last resort, show the login form
|
||||||
|
return False
|
||||||
|
|
||||||
|
actual_decorator = user_passes_test(check_manager, login_url=login_url)
|
||||||
|
if function:
|
||||||
|
return actual_decorator(function)
|
||||||
|
return actual_decorator
|
|
@ -0,0 +1,62 @@
|
||||||
|
# lingo - payment and billing system
|
||||||
|
# Copyright (C) 2022 Entr'ouvert
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import logout as auth_logout
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.shortcuts import resolve_url
|
||||||
|
from django.utils.http import quote
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
if 'mellon' in settings.INSTALLED_APPS:
|
||||||
|
from mellon.utils import get_idps # pylint: disable=import-error
|
||||||
|
else:
|
||||||
|
|
||||||
|
def get_idps():
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class LoginView(auth_views.LoginView):
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
if any(get_idps()):
|
||||||
|
if 'next' not in request.GET:
|
||||||
|
return HttpResponseRedirect(resolve_url('mellon_login'))
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
resolve_url('mellon_login') + '?next=' + quote(request.GET.get('next'))
|
||||||
|
)
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
login = LoginView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
def logout(request, next_page=None):
|
||||||
|
if any(get_idps()):
|
||||||
|
return HttpResponseRedirect(resolve_url('mellon_logout'))
|
||||||
|
auth_logout(request)
|
||||||
|
if next_page is not None:
|
||||||
|
next_page = resolve_url(next_page)
|
||||||
|
else:
|
||||||
|
next_page = '/'
|
||||||
|
return HttpResponseRedirect(next_page)
|
||||||
|
|
||||||
|
|
||||||
|
class HomepageView(TemplateView):
|
||||||
|
template_name = 'lingo/homepage.html'
|
||||||
|
|
||||||
|
|
||||||
|
homepage = HomepageView.as_view()
|
|
@ -0,0 +1,23 @@
|
||||||
|
# lingo - payment and billing system
|
||||||
|
# Copyright (C) 2022 Entr'ouvert
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lingo.settings")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lingo.settings")
|
||||||
|
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
|
execute_from_command_line(sys.argv)
|
|
@ -0,0 +1,130 @@
|
||||||
|
[MASTER]
|
||||||
|
profile=no
|
||||||
|
persistent=yes
|
||||||
|
ignore=vendor,Bouncers,ezt.py
|
||||||
|
cache-size=500
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
disable=
|
||||||
|
abstract-method,
|
||||||
|
arguments-differ,
|
||||||
|
assignment-from-none,
|
||||||
|
attribute-defined-outside-init,
|
||||||
|
bad-super-call,
|
||||||
|
broad-except,
|
||||||
|
consider-using-dict-comprehension,
|
||||||
|
consider-using-f-string,
|
||||||
|
consider-using-set-comprehension,
|
||||||
|
cyclic-import,
|
||||||
|
duplicate-code,
|
||||||
|
exec-used,
|
||||||
|
fixme,
|
||||||
|
global-variable-undefined,
|
||||||
|
import-outside-toplevel,
|
||||||
|
inconsistent-return-statements,
|
||||||
|
invalid-name,
|
||||||
|
keyword-arg-before-vararg,
|
||||||
|
missing-class-docstring,
|
||||||
|
missing-function-docstring,
|
||||||
|
missing-module-docstring,
|
||||||
|
no-else-return,
|
||||||
|
no-member,
|
||||||
|
no-self-use,
|
||||||
|
non-parent-init-called,
|
||||||
|
not-callable,
|
||||||
|
possibly-unused-variable,
|
||||||
|
protected-access,
|
||||||
|
raise-missing-from,
|
||||||
|
redefined-argument-from-local,
|
||||||
|
redefined-builtin,
|
||||||
|
redefined-outer-name,
|
||||||
|
signature-differs,
|
||||||
|
stop-iteration-return,
|
||||||
|
super-init-not-called,
|
||||||
|
superfluous-parens,
|
||||||
|
too-many-ancestors,
|
||||||
|
too-many-arguments,
|
||||||
|
too-many-branches,
|
||||||
|
too-many-instance-attributes,
|
||||||
|
too-many-lines,
|
||||||
|
too-many-locals,
|
||||||
|
too-many-nested-blocks,
|
||||||
|
too-many-return-statements,
|
||||||
|
too-many-statements,
|
||||||
|
undefined-loop-variable,
|
||||||
|
unnecessary-comprehension,
|
||||||
|
unspecified-encoding,
|
||||||
|
unsubscriptable-object,
|
||||||
|
unsupported-membership-test,
|
||||||
|
unused-argument,
|
||||||
|
use-a-generator,
|
||||||
|
use-implicit-booleaness-not-comparison
|
||||||
|
|
||||||
|
|
||||||
|
[REPORTS]
|
||||||
|
output-format=parseable
|
||||||
|
include-ids=yes
|
||||||
|
|
||||||
|
|
||||||
|
[BASIC]
|
||||||
|
no-docstring-rgx=__.*__|_.*
|
||||||
|
class-rgx=[A-Z_][a-zA-Z0-9_]+$
|
||||||
|
function-rgx=[a-zA_][a-zA-Z0-9_]{2,70}$
|
||||||
|
method-rgx=[a-z_][a-zA-Z0-9_]{2,70}$
|
||||||
|
const-rgx=(([A-Z_][A-Z0-9_]*)|([a-z_][a-z0-9_]*)|(__.*__)|register|urlpatterns)$
|
||||||
|
good-names=_,i,j,k,e,x,Run,,setUp,tearDown,r,p,s,v,fd
|
||||||
|
|
||||||
|
[TYPECHECK]
|
||||||
|
|
||||||
|
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||||
|
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||||
|
ignore-mixin-members=yes
|
||||||
|
|
||||||
|
# List of classes names for which member attributes should not be checked
|
||||||
|
# (useful for classes with attributes dynamically set).
|
||||||
|
ignored-classes=SQLObject,WSGIRequest,Publisher,NullSessionManager
|
||||||
|
|
||||||
|
# When zope mode is activated, add a predefined set of Zope acquired attributes
|
||||||
|
# to generated-members.
|
||||||
|
zope=no
|
||||||
|
|
||||||
|
# List of members which are set dynamically and missed by pylint inference
|
||||||
|
# system, and so shouldn't trigger E0201 when accessed.
|
||||||
|
generated-members=objects,DoesNotExist,id,pk,_meta,base_fields,context
|
||||||
|
|
||||||
|
# List of method names used to declare (i.e. assign) instance attributes
|
||||||
|
defining-attr-methods=__init__,__new__,setUp
|
||||||
|
|
||||||
|
|
||||||
|
[VARIABLES]
|
||||||
|
init-import=no
|
||||||
|
dummy-variables-rgx=_|dummy
|
||||||
|
additional-builtins=_,N_,ngettext
|
||||||
|
good-names=_,i,j,k,e,x,Run,,setUp,tearDown,r,p,s,v,fd
|
||||||
|
|
||||||
|
[SIMILARITIES]
|
||||||
|
min-similarity-lines=6
|
||||||
|
ignore-comments=yes
|
||||||
|
ignore-docstrings=yes
|
||||||
|
|
||||||
|
|
||||||
|
[MISCELLANEOUS]
|
||||||
|
notes=FIXME,XXX,TODO
|
||||||
|
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
max-line-length=160
|
||||||
|
max-module-lines=2000
|
||||||
|
indent-string=' '
|
||||||
|
|
||||||
|
|
||||||
|
[DESIGN]
|
||||||
|
max-args=10
|
||||||
|
max-locals=15
|
||||||
|
max-returns=6
|
||||||
|
max-branchs=12
|
||||||
|
max-statements=50
|
||||||
|
max-parents=7
|
||||||
|
max-attributes=7
|
||||||
|
min-public-methods=0
|
||||||
|
max-public-methods=50
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e -x
|
||||||
|
env
|
||||||
|
|
||||||
|
pylint -f parseable --rcfile pylint.rc "$@" | tee pylint.out; test $PIPESTATUS -eq 0
|
|
@ -0,0 +1,176 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import itertools
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from distutils.cmd import Command
|
||||||
|
from distutils.command.build import build as _build
|
||||||
|
from distutils.command.sdist import sdist
|
||||||
|
from distutils.errors import CompileError
|
||||||
|
from distutils.spawn import find_executable
|
||||||
|
|
||||||
|
from setuptools import find_packages, setup
|
||||||
|
from setuptools.command.install_lib import install_lib as _install_lib
|
||||||
|
|
||||||
|
|
||||||
|
class eo_sdist(sdist):
|
||||||
|
def run(self):
|
||||||
|
if os.path.exists('VERSION'):
|
||||||
|
os.remove('VERSION')
|
||||||
|
version = get_version()
|
||||||
|
with open('VERSION', 'w') as fd:
|
||||||
|
fd.write(version)
|
||||||
|
with open('lingo/version.py', 'w') as fd:
|
||||||
|
fd.write('VERSION = %r' % version)
|
||||||
|
sdist.run(self)
|
||||||
|
if os.path.exists('VERSION'):
|
||||||
|
os.remove('VERSION')
|
||||||
|
|
||||||
|
|
||||||
|
def get_version():
|
||||||
|
"""Use the VERSION, if absent generates a version with git describe, if not
|
||||||
|
tag exists, take 0.0- and add the length of the commit log.
|
||||||
|
"""
|
||||||
|
if os.path.exists('VERSION'):
|
||||||
|
with open('VERSION') as v:
|
||||||
|
return v.read()
|
||||||
|
if os.path.exists('.git'):
|
||||||
|
p = subprocess.Popen(
|
||||||
|
['git', 'describe', '--dirty=.dirty', '--match=v*'],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
result = p.communicate()[0]
|
||||||
|
if p.returncode == 0:
|
||||||
|
result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
|
||||||
|
if '-' in result: # not a tagged version
|
||||||
|
real_number, commit_count, commit_hash = result.split('-', 2)
|
||||||
|
version = '%s.post%s+%s' % (real_number, commit_count, commit_hash)
|
||||||
|
else:
|
||||||
|
version = result
|
||||||
|
return version
|
||||||
|
else:
|
||||||
|
return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines())
|
||||||
|
return '0.0'
|
||||||
|
|
||||||
|
|
||||||
|
def data_tree(destdir, sourcedir):
|
||||||
|
extensions = ['.css', '.png', '.jpeg', '.jpg', '.gif', '.xml', '.html', '.js']
|
||||||
|
r = []
|
||||||
|
for root, dirs, files in os.walk(sourcedir):
|
||||||
|
l = [os.path.join(root, x) for x in files if os.path.splitext(x)[1] in extensions]
|
||||||
|
r.append((root.replace(sourcedir, destdir, 1), l))
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class compile_translations(Command):
|
||||||
|
description = 'compile message catalogs to MO files via django compilemessages'
|
||||||
|
user_options = []
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
orig_dir = os.getcwd()
|
||||||
|
try:
|
||||||
|
from django.core.management import call_command
|
||||||
|
|
||||||
|
for path, dirs, files in os.walk('lingo'):
|
||||||
|
if 'locale' not in dirs:
|
||||||
|
continue
|
||||||
|
curdir = os.getcwd()
|
||||||
|
os.chdir(os.path.realpath(path))
|
||||||
|
call_command('compilemessages')
|
||||||
|
os.chdir(curdir)
|
||||||
|
except ImportError:
|
||||||
|
sys.stderr.write('!!! Please install Django >= 1.4 to build translations\n')
|
||||||
|
os.chdir(orig_dir)
|
||||||
|
|
||||||
|
|
||||||
|
class compile_scss(Command):
|
||||||
|
description = 'compile scss files into css files'
|
||||||
|
user_options = []
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
sass_bin = None
|
||||||
|
for program in ('sassc', 'sass'):
|
||||||
|
sass_bin = find_executable(program)
|
||||||
|
if sass_bin:
|
||||||
|
break
|
||||||
|
if not sass_bin:
|
||||||
|
raise CompileError(
|
||||||
|
'A sass compiler is required but none was found. See sass-lang.com for choices.'
|
||||||
|
)
|
||||||
|
|
||||||
|
for path, dirnames, filenames in os.walk('lingo'):
|
||||||
|
for filename in filenames:
|
||||||
|
if not filename.endswith('.scss'):
|
||||||
|
continue
|
||||||
|
if filename.startswith('_'):
|
||||||
|
continue
|
||||||
|
subprocess.check_call(
|
||||||
|
[
|
||||||
|
sass_bin,
|
||||||
|
'%s/%s' % (path, filename),
|
||||||
|
'%s/%s' % (path, filename.replace('.scss', '.css')),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class build(_build):
|
||||||
|
sub_commands = [('compile_translations', None)] + _build.sub_commands
|
||||||
|
|
||||||
|
|
||||||
|
class install_lib(_install_lib):
|
||||||
|
def run(self):
|
||||||
|
self.run_command('compile_translations')
|
||||||
|
_install_lib.run(self)
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='lingo',
|
||||||
|
version=get_version(),
|
||||||
|
description='Payments and Bills System',
|
||||||
|
author='Thomas NOËL',
|
||||||
|
author_email='tnoel@entrouvert.com',
|
||||||
|
packages=find_packages(exclude=['tests']),
|
||||||
|
include_package_data=True,
|
||||||
|
scripts=('manage.py',),
|
||||||
|
url='https://dev.entrouvert.org/projects/lingo/',
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 4 - Beta',
|
||||||
|
'Environment :: Web Environment',
|
||||||
|
'Framework :: Django',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
],
|
||||||
|
install_requires=[
|
||||||
|
'django>=2.2, <2.3',
|
||||||
|
'gadjo>=0.53',
|
||||||
|
'requests',
|
||||||
|
'eopayment>=1.60',
|
||||||
|
'djangorestframework>=3.3, <3.10',
|
||||||
|
],
|
||||||
|
zip_safe=False,
|
||||||
|
cmdclass={
|
||||||
|
'build': build,
|
||||||
|
'compile_translations': compile_translations,
|
||||||
|
'install_lib': install_lib,
|
||||||
|
'sdist': eo_sdist,
|
||||||
|
},
|
||||||
|
)
|
|
@ -0,0 +1,28 @@
|
||||||
|
import django_webtest
|
||||||
|
import pytest
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def media(settings, tmpdir):
|
||||||
|
settings.MEDIA_ROOT = str(tmpdir.mkdir('media'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def app(request):
|
||||||
|
wtm = django_webtest.WebTestMixin()
|
||||||
|
wtm._patch_settings()
|
||||||
|
request.addfinalizer(wtm._unpatch_settings)
|
||||||
|
cache.clear()
|
||||||
|
return django_webtest.DjangoTestApp()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def simple_user():
|
||||||
|
return User.objects.create_user('user', password='user')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def admin_user():
|
||||||
|
return User.objects.create_superuser('admin', email=None, password='admin')
|
|
@ -0,0 +1,20 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||||
|
},
|
||||||
|
'dummy': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
|
||||||
|
}
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
|
'TEST': {
|
||||||
|
'NAME': ('lingo-test-%s' % os.environ.get("BRANCH_NAME", "").replace('/', '-'))[:63],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_homepage(app):
|
||||||
|
assert app.get('/', status=200)
|
|
@ -0,0 +1,29 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def login(app, username='admin', password='admin'):
|
||||||
|
login_page = app.get('/login/')
|
||||||
|
login_form = login_page.forms[0]
|
||||||
|
login_form['username'] = username
|
||||||
|
login_form['password'] = password
|
||||||
|
resp = login_form.submit()
|
||||||
|
assert resp.status_int == 302
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def test_unlogged_access(app):
|
||||||
|
# connect while not being logged in
|
||||||
|
assert app.get('/manage/', status=302).location.endswith('/login/?next=/manage/')
|
||||||
|
|
||||||
|
|
||||||
|
def test_simple_user_access(app, simple_user):
|
||||||
|
# connect while being logged as a simple user
|
||||||
|
app = login(app, username='user', password='user')
|
||||||
|
assert app.get('/manage/', status=403)
|
||||||
|
|
||||||
|
|
||||||
|
def test_access(app, admin_user):
|
||||||
|
app = login(app)
|
||||||
|
assert app.get('/manage/', status=200)
|
|
@ -0,0 +1,59 @@
|
||||||
|
[tox]
|
||||||
|
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/lingo/{env:BRANCH_NAME:}
|
||||||
|
envlist = coverage-py3-django22-codestyle, pylint
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
usedevelop = True
|
||||||
|
setenv =
|
||||||
|
DJANGO_SETTINGS_MODULE=lingo.settings
|
||||||
|
LINGO_SETTINGS_FILE=tests/settings.py
|
||||||
|
TOX_WORK_DIR={toxworkdir}
|
||||||
|
SETUPTOOLS_USE_DISTUTILS=stdlib
|
||||||
|
coverage: COVERAGE=--cov-report xml --cov-report html --cov=lingo/ --cov-config .coveragerc -v
|
||||||
|
DB_ENGINE=django.db.backends.postgresql_psycopg2
|
||||||
|
passenv =
|
||||||
|
BRANCH_NAME
|
||||||
|
deps =
|
||||||
|
django22: django>=2.2,<2.3
|
||||||
|
pytest-cov
|
||||||
|
pytest-django
|
||||||
|
pytest-freezegun
|
||||||
|
pytest!=5.3.3
|
||||||
|
WebTest
|
||||||
|
mock<4
|
||||||
|
httmock
|
||||||
|
pylint
|
||||||
|
pylint-django
|
||||||
|
django-webtest<1.9.3
|
||||||
|
pyquery
|
||||||
|
psycopg2-binary<2.9
|
||||||
|
django-mellon>=1.13
|
||||||
|
pre-commit
|
||||||
|
commands =
|
||||||
|
./getlasso3.sh
|
||||||
|
python manage.py compilemessages
|
||||||
|
py.test {env:COVERAGE:} {posargs: --junitxml=junit-{envname}.xml tests/}
|
||||||
|
codestyle: pre-commit run --all-files --show-diff-on-failure
|
||||||
|
|
||||||
|
[testenv:pylint]
|
||||||
|
setenv =
|
||||||
|
DJANGO_SETTINGS_MODULE=lingo.settings
|
||||||
|
LINGO_SETTINGS_FILE=tests/settings.py
|
||||||
|
TOX_WORK_DIR={toxworkdir}
|
||||||
|
SETUPTOOLS_USE_DISTUTILS=stdlib
|
||||||
|
DB_ENGINE=django.db.backends.postgresql_psycopg2
|
||||||
|
deps =
|
||||||
|
pytest-django
|
||||||
|
pytest!=5.3.3
|
||||||
|
WebTest
|
||||||
|
mock<4
|
||||||
|
httmock
|
||||||
|
django-mellon>=1.13
|
||||||
|
pylint
|
||||||
|
pylint-django
|
||||||
|
django-webtest<1.9.3
|
||||||
|
pyquery
|
||||||
|
psycopg2-binary<2.9
|
||||||
|
commands =
|
||||||
|
./getlasso3.sh
|
||||||
|
./pylint.sh lingo/ tests/
|
Loading…
Reference in New Issue