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