start a Publik Django application (#64765)
This commit is contained in:
parent
05fb86a97f
commit
dbdf44777f
|
@ -11,3 +11,6 @@ lingo.egg-info/
|
|||
.cache
|
||||
.coverage
|
||||
.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