mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-07-04 06:42:13 +02:00
Compare commits
53 Commits
9d8f47115c
...
faster_ci
Author | SHA1 | Date | |
---|---|---|---|
3eed93e346 | |||
4da523a1ba | |||
e74ff54468 | |||
2e49c9ffbd | |||
d20a1038a8 | |||
f6b711bb1b | |||
893d87a9e1 | |||
9f3323c73e | |||
c57f81b920 | |||
0636d84286 | |||
ed06901fae | |||
28932f316b | |||
9b50ba722c | |||
3e3e61d23f | |||
1129815ca3 | |||
c13172d3ff | |||
fcc4121225 | |||
a06f355559 | |||
08df5fcccd | |||
b6c0f9758d | |||
a23093851f | |||
d5a9bf175f
|
|||
d803ab5ec2 | |||
d7a537b6b5 | |||
0941ee954d | |||
fd11d96d95 | |||
4bfc057454 | |||
b597a6ac5b
|
|||
a704b92c3d | |||
53090b1a21 | |||
c49af0b83a | |||
5a05997d9d
|
|||
c109cd3ddd
|
|||
84304971d7
|
|||
b8b781f9a2 | |||
002128eed2 | |||
8d71783c42 | |||
a6f23df7d5
|
|||
d9c97628e2
|
|||
893534955d
|
|||
dfbf9972c2
|
|||
b5f3b3ffc1
|
|||
3aad4e7398
|
|||
b4a1b513cc
|
|||
c0c64f225c | |||
0171f16311 | |||
e1f647bd02 | |||
39fd3a2471 | |||
1072e227b8 | |||
cbf7e6fe6c | |||
950922d041 | |||
78fe070cd3 | |||
51d5733578 |
3
.ansible-lint
Normal file
3
.ansible-lint
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
skip_list:
|
||||||
|
- command-instead-of-shell # Use shell only when shell functionality is required
|
||||||
|
- experimental # all rules tagged as experimental
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -47,3 +47,8 @@ backups/
|
|||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
|
||||||
|
# ansibles customs host
|
||||||
|
ansible/host_vars/*.yaml
|
||||||
|
!ansible/host_vars/bde*
|
||||||
|
ansible/hosts
|
||||||
|
@ -10,50 +10,22 @@ variables:
|
|||||||
# Debian Buster
|
# Debian Buster
|
||||||
py37-django22:
|
py37-django22:
|
||||||
stage: test
|
stage: test
|
||||||
image: debian:buster-backports
|
image: otthorn/nk20_ci_37
|
||||||
before_script:
|
|
||||||
- >
|
|
||||||
apt-get update &&
|
|
||||||
apt-get install --no-install-recommends -t buster-backports -y
|
|
||||||
python3-django python3-django-crispy-forms
|
|
||||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
|
||||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
|
||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
|
||||||
script: tox -e py37-django22
|
script: tox -e py37-django22
|
||||||
|
|
||||||
# Ubuntu 20.04
|
# Ubuntu 20.04
|
||||||
py38-django22:
|
py38-django22:
|
||||||
stage: test
|
stage: test
|
||||||
image: ubuntu:20.04
|
image: otthorn/nk20_ci_38
|
||||||
before_script:
|
|
||||||
# Fix tzdata prompt
|
|
||||||
- ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
|
||||||
- >
|
|
||||||
apt-get update &&
|
|
||||||
apt-get install --no-install-recommends -y
|
|
||||||
python3-django python3-django-crispy-forms
|
|
||||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
|
||||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
|
||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
|
||||||
script: tox -e py38-django22
|
script: tox -e py38-django22
|
||||||
|
|
||||||
# Debian Bullseye
|
# Debian Bullseye
|
||||||
py39-django22:
|
py39-django22:
|
||||||
stage: test
|
stage: test
|
||||||
image: debian:bullseye
|
image: otthorn/nk20_ci_39
|
||||||
before_script:
|
|
||||||
- >
|
|
||||||
apt-get update &&
|
|
||||||
apt-get install --no-install-recommends -y
|
|
||||||
python3-django python3-django-crispy-forms
|
|
||||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
|
||||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
|
||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
|
||||||
script: tox -e py39-django22
|
script: tox -e py39-django22
|
||||||
|
|
||||||
|
# Tox linter
|
||||||
linters:
|
linters:
|
||||||
stage: quality-assurance
|
stage: quality-assurance
|
||||||
image: debian:buster-backports
|
image: debian:buster-backports
|
||||||
@ -64,6 +36,20 @@ linters:
|
|||||||
# Be nice to new contributors, but please use `tox`
|
# Be nice to new contributors, but please use `tox`
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
|
||||||
|
# Ansible linter
|
||||||
|
ansible-linter:
|
||||||
|
stage: quality-assurance
|
||||||
|
image: otthorn/nk20_ci_ansiblelint
|
||||||
|
script: ansible-lint ansible/
|
||||||
|
|
||||||
|
# Docker linter
|
||||||
|
docker-linter:
|
||||||
|
stage: quality-assurance
|
||||||
|
image: hadolint/hadolint
|
||||||
|
script:
|
||||||
|
- hadolint -c .hadolint Dockerfile
|
||||||
|
- hadolint -c .hadolint docker_ci/Dockerfile.*
|
||||||
|
|
||||||
# Compile documentation
|
# Compile documentation
|
||||||
documentation:
|
documentation:
|
||||||
stage: docs
|
stage: docs
|
||||||
|
4
.hadolint
Normal file
4
.hadolint
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
ignored:
|
||||||
|
- DL3008 # Do not force to pin version in apt (Debian)
|
||||||
|
- DL3013 # Do not force to pin version in pip (PyPI)
|
||||||
|
- DL3018 # Do not force to pin version in apk (Alpine)
|
20
README.md
20
README.md
@ -69,13 +69,31 @@ accessible depuis l'ensemble de votre réseau, pratique pour tester le rendu
|
|||||||
de la note sur un téléphone !
|
de la note sur un téléphone !
|
||||||
|
|
||||||
## Installation d'une instance de production
|
## Installation d'une instance de production
|
||||||
|
Pour déployer facilement la note il est possible d'utiliser le playbook Ansible (sinon vous pouvez toujours le faire a la main, voir plus bas).
|
||||||
|
### Avec ansible
|
||||||
|
Il vous faudra un serveur sous debian ou ubuntu connecté à internet et que vous souhaiterez accéder à cette instance de la note sur `note.nomdedomaine.tld`.
|
||||||
|
|
||||||
|
0. Installer Ansible sur votre machine personnelle.
|
||||||
|
|
||||||
|
0. (bis) cloner le dépot sur votre machine personelle.
|
||||||
|
|
||||||
|
1. Copier le fichier `ansible/host_example`
|
||||||
|
``` bash
|
||||||
|
$ cp ansible/hosts_example ansible/hosts
|
||||||
|
```
|
||||||
|
et ajouter sous [dev] et/ou [prod] les serveurs sur lesquels vous souhaitez installer la note.
|
||||||
|
2. Créer un fichier `ansible/host_vars/<note.nomdedomaine.tld.yaml>` sur le modèle des fichiers existants dans `ansible/hosts` et compléter les variables nécessaires.
|
||||||
|
|
||||||
|
3. lancer `ansible/base.yaml -l <nomdedomaine.tld.yaml>`
|
||||||
|
4. Aller vous faire un café, ca peux durer un moment.
|
||||||
|
|
||||||
|
### Installation manuelle
|
||||||
|
|
||||||
**En production on souhaite absolument utiliser les modules Python packagées dans le gestionnaire de paquet.**
|
**En production on souhaite absolument utiliser les modules Python packagées dans le gestionnaire de paquet.**
|
||||||
Cela permet de mettre à jour facilement les dépendances critiques telles que Django.
|
Cela permet de mettre à jour facilement les dépendances critiques telles que Django.
|
||||||
|
|
||||||
L'installation d'une instance de production néccessite **une installation de Debian Buster ou d'Ubuntu 20.04**.
|
L'installation d'une instance de production néccessite **une installation de Debian Buster ou d'Ubuntu 20.04**.
|
||||||
|
|
||||||
Pour aller vite vous pouvez lancer le Playbook Ansible fournit dans ce dépôt en l'adaptant.
|
|
||||||
Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
||||||
|
|
||||||
0. Sous Debian Buster, **activer Debian Backports.** En effet Django 2.2 LTS n'est que disponible dans les backports.
|
0. Sous Debian Buster, **activer Debian Backports.** En effet Django 2.2 LTS n'est que disponible dans les backports.
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
prompt: "Password of the database (leave it blank to skip database init)"
|
prompt: "Password of the database (leave it blank to skip database init)"
|
||||||
private: yes
|
private: yes
|
||||||
vars:
|
vars:
|
||||||
mirror: deb.debian.org
|
mirror: mirror.crans.org
|
||||||
roles:
|
roles:
|
||||||
- 1-apt-basic
|
- 1-apt-basic
|
||||||
- 2-nk20
|
- 2-nk20
|
||||||
|
@ -3,3 +3,4 @@ note:
|
|||||||
server_name: note-beta.crans.org
|
server_name: note-beta.crans.org
|
||||||
git_branch: beta
|
git_branch: beta
|
||||||
cron_enabled: false
|
cron_enabled: false
|
||||||
|
email: notekfet2020@lists.crans.org
|
||||||
|
@ -3,3 +3,4 @@ note:
|
|||||||
server_name: note-dev.crans.org
|
server_name: note-dev.crans.org
|
||||||
git_branch: beta
|
git_branch: beta
|
||||||
cron_enabled: false
|
cron_enabled: false
|
||||||
|
email: notekfet2020@lists.crans.org
|
@ -3,3 +3,4 @@ note:
|
|||||||
server_name: note.crans.org
|
server_name: note.crans.org
|
||||||
git_branch: master
|
git_branch: master
|
||||||
cron_enabled: true
|
cron_enabled: true
|
||||||
|
email: notekfet2020@lists.crans.org
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[dev]
|
[dev]
|
||||||
bde3-virt.adh.crans.org
|
bde-note-dev.adh.crans.org
|
||||||
bde-nk20-beta.adh.crans.org
|
bde-nk20-beta.adh.crans.org
|
||||||
|
|
||||||
[prod]
|
[prod]
|
@ -3,11 +3,12 @@
|
|||||||
apt_repository:
|
apt_repository:
|
||||||
repo: deb http://{{ mirror }}/debian buster-backports main
|
repo: deb http://{{ mirror }}/debian buster-backports main
|
||||||
state: present
|
state: present
|
||||||
|
when: ansible_facts['distribution'] == "Debian"
|
||||||
|
|
||||||
- name: Install note_kfet APT dependencies
|
- name: Install note_kfet APT dependencies
|
||||||
apt:
|
apt:
|
||||||
update_cache: true
|
update_cache: true
|
||||||
default_release: buster-backports
|
default_release: "{{ 'buster-backports' if ansible_facts['distribution'] == 'Debian' }}"
|
||||||
install_recommends: false
|
install_recommends: false
|
||||||
name:
|
name:
|
||||||
# Common tools
|
# Common tools
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
- name: Use default env vars (should be updated!)
|
- name: Use default env vars (should be updated!)
|
||||||
template:
|
template:
|
||||||
src: "env_example"
|
src: "env.j2"
|
||||||
dest: "/var/www/note_kfet/.env"
|
dest: "/var/www/note_kfet/.env"
|
||||||
mode: 0644
|
mode: 0644
|
||||||
force: false
|
force: false
|
||||||
@ -36,3 +36,13 @@
|
|||||||
dest: /etc/cron.d/note
|
dest: /etc/cron.d/note
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
|
|
||||||
|
- name: Set default directory to /var/www/note_kfet
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/skel/.bashrc
|
||||||
|
line: 'cd /var/www/note_kfet'
|
||||||
|
|
||||||
|
- name: Automatically source Python virtual environment
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/skel/.bashrc
|
||||||
|
line: 'source /var/www/note_kfet/env/bin/activate'
|
||||||
|
23
ansible/roles/2-nk20/templates/env.j2
Normal file
23
ansible/roles/2-nk20/templates/env.j2
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
DJANGO_APP_STAGE=prod
|
||||||
|
# Only used in dev mode, change to "postgresql" if you want to use PostgreSQL in dev
|
||||||
|
DJANGO_DEV_STORE_METHOD=sqlite
|
||||||
|
DJANGO_DB_HOST=localhost
|
||||||
|
DJANGO_DB_NAME=note_db
|
||||||
|
DJANGO_DB_USER=note
|
||||||
|
DJANGO_DB_PASSWORD={{ DB_PASSWORD }}
|
||||||
|
DJANGO_DB_PORT=
|
||||||
|
DJANGO_SECRET_KEY=CHANGE_ME
|
||||||
|
DJANGO_SETTINGS_MODULE=note_kfet.settings
|
||||||
|
CONTACT_EMAIL=tresorerie.bde@localhost
|
||||||
|
NOTE_URL= {{note.server_name}}
|
||||||
|
|
||||||
|
# Config for mails. Only used in production
|
||||||
|
NOTE_MAIL=notekfet@localhost
|
||||||
|
EMAIL_HOST=smtp.localhost
|
||||||
|
EMAIL_PORT=25
|
||||||
|
EMAIL_USER=notekfet@localhost
|
||||||
|
EMAIL_PASSWORD=CHANGE_ME
|
||||||
|
|
||||||
|
# Wiki configuration
|
||||||
|
WIKI_USER=NoteKfet2020
|
||||||
|
WIKI_PASSWORD=
|
@ -9,6 +9,11 @@
|
|||||||
retries: 3
|
retries: 3
|
||||||
until: pkg_result is succeeded
|
until: pkg_result is succeeded
|
||||||
|
|
||||||
|
- name: Check if certificate already exists.
|
||||||
|
stat:
|
||||||
|
path: /etc/letsencrypt/live/{{note.server_name}}/cert.pem
|
||||||
|
register: letsencrypt_cert
|
||||||
|
|
||||||
- name: Create /etc/letsencrypt/conf.d
|
- name: Create /etc/letsencrypt/conf.d
|
||||||
file:
|
file:
|
||||||
path: /etc/letsencrypt/conf.d
|
path: /etc/letsencrypt/conf.d
|
||||||
@ -19,3 +24,17 @@
|
|||||||
src: "letsencrypt/conf.d/nk20.ini.j2"
|
src: "letsencrypt/conf.d/nk20.ini.j2"
|
||||||
dest: "/etc/letsencrypt/conf.d/nk20.ini"
|
dest: "/etc/letsencrypt/conf.d/nk20.ini"
|
||||||
mode: 0644
|
mode: 0644
|
||||||
|
|
||||||
|
- name: Stop services to allow certbot to generate a cert.
|
||||||
|
service:
|
||||||
|
name: nginx
|
||||||
|
state: stopped
|
||||||
|
|
||||||
|
- name: Generate new certificate if one doesn't exist.
|
||||||
|
shell: "certbot certonly --non-interactive --agree-tos --config /etc/letsencrypt/conf.d/nk20.ini -d {{note.server_name}}"
|
||||||
|
when: letsencrypt_cert.stat.exists == False
|
||||||
|
|
||||||
|
- name: Restart services to allow certbot to generate a cert.
|
||||||
|
service:
|
||||||
|
name: nginx
|
||||||
|
state: started
|
||||||
|
@ -10,7 +10,7 @@ rsa-key-size = 4096
|
|||||||
# server = https://acme-staging.api.letsencrypt.org/directory
|
# server = https://acme-staging.api.letsencrypt.org/directory
|
||||||
|
|
||||||
# Uncomment and update to register with the specified e-mail address
|
# Uncomment and update to register with the specified e-mail address
|
||||||
email = notekfet2020@lists.crans.org
|
email = {{ note.email }}
|
||||||
|
|
||||||
# Uncomment to use a text interface instead of ncurses
|
# Uncomment to use a text interface instead of ncurses
|
||||||
text = True
|
text = True
|
||||||
|
@ -11,14 +11,14 @@
|
|||||||
until: pkg_result is succeeded
|
until: pkg_result is succeeded
|
||||||
|
|
||||||
- name: Create role note
|
- name: Create role note
|
||||||
when: "DB_PASSWORD|bool" # If the password is not defined, skip the installation
|
when: DB_PASSWORD|length > 0 # If the password is not defined, skip the installation
|
||||||
postgresql_user:
|
postgresql_user:
|
||||||
name: note
|
name: note
|
||||||
password: "{{ DB_PASSWORD }}"
|
password: "{{ DB_PASSWORD }}"
|
||||||
become_user: postgres
|
become_user: postgres
|
||||||
|
|
||||||
- name: Create NK20 database
|
- name: Create NK20 database
|
||||||
when: "DB_PASSWORD|bool"
|
when: DB_PASSWORD|length >0
|
||||||
postgresql_db:
|
postgresql_db:
|
||||||
name: note_db
|
name: note_db
|
||||||
owner: note
|
owner: note
|
||||||
|
@ -658,8 +658,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
if club.name != "Kfet" and club.parent_club and not Membership.objects.filter(
|
if club.name != "Kfet" and club.parent_club and not Membership.objects.filter(
|
||||||
user=form.instance.user,
|
user=form.instance.user,
|
||||||
club=club.parent_club,
|
club=club.parent_club,
|
||||||
date_start__gte=club.parent_club.membership_start,
|
date_start__lte=timezone.now(),
|
||||||
date_end__lte=club.parent_club.membership_end,
|
date_end__gte=club.parent_club.membership_end,
|
||||||
).exists():
|
).exists():
|
||||||
form.add_error('user', _('User is not a member of the parent club') + ' ' + club.parent_club.name)
|
form.add_error('user', _('User is not a member of the parent club') + ' ' + club.parent_club.name)
|
||||||
error = True
|
error = True
|
||||||
|
@ -223,7 +223,8 @@ class Transaction(PolymorphicModel):
|
|||||||
# Check that the amounts stay between big integer bounds
|
# Check that the amounts stay between big integer bounds
|
||||||
diff_source, diff_dest = self.validate()
|
diff_source, diff_dest = self.validate()
|
||||||
|
|
||||||
if not self.source.is_active or not self.destination.is_active:
|
if not (hasattr(self, '_force_save') and self._force_save) \
|
||||||
|
and (not self.source.is_active or not self.destination.is_active):
|
||||||
raise ValidationError(_("The transaction can't be saved since the source note "
|
raise ValidationError(_("The transaction can't be saved since the source note "
|
||||||
"or the destination note is not active."))
|
"or the destination note is not active."))
|
||||||
|
|
||||||
@ -271,7 +272,7 @@ class RecurrentTransaction(Transaction):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.template.destination != self.destination:
|
if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("The destination of this transaction must equal to the destination of the template."))
|
_("The destination of this transaction must equal to the destination of the template."))
|
||||||
return super().clean()
|
return super().clean()
|
||||||
|
@ -43,4 +43,5 @@ def delete_transaction(instance, **_kwargs):
|
|||||||
"""
|
"""
|
||||||
if not hasattr(instance, "_no_signal"):
|
if not hasattr(instance, "_no_signal"):
|
||||||
instance.valid = False
|
instance.valid = False
|
||||||
|
instance._force_save = True
|
||||||
instance.save()
|
instance.save()
|
||||||
|
@ -223,13 +223,14 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
|
|||||||
const newBalance = source.balance - quantity * amount
|
const newBalance = source.balance - quantity * amount
|
||||||
if (newBalance <= -5000) {
|
if (newBalance <= -5000) {
|
||||||
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
|
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
|
||||||
'but the emitter note %s is very negative.', [source_alias, source_alias])), 'danger', 30000)
|
'but the emitter note %s is very negative.'), [source_alias, source_alias]), 'danger', 30000)
|
||||||
} else if (newBalance < 0) {
|
} else if (newBalance < 0) {
|
||||||
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
|
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
|
||||||
'but the emitter note %s is negative.', [source_alias, source_alias])), 'warning', 30000)
|
'but the emitter note %s is negative.'), [source_alias, source_alias]), 'warning', 30000)
|
||||||
}
|
}
|
||||||
if (source.membership && source.membership.date_end < new Date().toISOString()) {
|
if (source.membership && source.membership.date_end < new Date().toISOString()) {
|
||||||
addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.', [source_alias])), 'danger', 30000)
|
addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.'), [source_alias]),
|
||||||
|
'danger', 30000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reset()
|
reset()
|
||||||
|
@ -302,7 +302,7 @@ $('#btn_transfer').click(function () {
|
|||||||
addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.'), [source.name]), 'danger', 30000)
|
addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.'), [source.name]), 'danger', 30000)
|
||||||
}
|
}
|
||||||
if (dest.note.membership && dest.note.membership.date_end < new Date().toISOString()) {
|
if (dest.note.membership && dest.note.membership.date_end < new Date().toISOString()) {
|
||||||
addMsg(interpolate(gettext('Warning, the destination note %s is no more a BDE member.'), [source.name]), 'danger', 30000)
|
addMsg(interpolate(gettext('Warning, the destination note %s is no more a BDE member.'), [dest.name]), 'danger', 30000)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNaN(source.note.balance)) {
|
if (!isNaN(source.note.balance)) {
|
||||||
|
@ -3024,7 +3024,9 @@
|
|||||||
24,
|
24,
|
||||||
25,
|
25,
|
||||||
26,
|
26,
|
||||||
27
|
27,
|
||||||
|
30,
|
||||||
|
33
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Submodule apps/scripts updated: dbe7bf6591...8ec7d68a16
@ -381,9 +381,14 @@ class SogeCredit(models.Model):
|
|||||||
tr.valid = True
|
tr.valid = True
|
||||||
tr.created_at = timezone.now()
|
tr.created_at = timezone.now()
|
||||||
tr.save()
|
tr.save()
|
||||||
self.credit_transaction.valid = False
|
if self.credit_transaction:
|
||||||
self.credit_transaction.reason += " (invalide)"
|
# If the soge credit is deleted while the user is not validated yet,
|
||||||
self.credit_transaction.save()
|
# there is not credit transaction.
|
||||||
|
# There is a credit transaction iff the user declares that no bank account
|
||||||
|
# was opened after the validation of the account.
|
||||||
|
self.credit_transaction.valid = False
|
||||||
|
self.credit_transaction.reason += " (invalide)"
|
||||||
|
self.credit_transaction.save()
|
||||||
super().delete(**kwargs)
|
super().delete(**kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
18
docker_ci/Dockerfile.37
Normal file
18
docker_ci/Dockerfile.37
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
FROM debian:buster-backports
|
||||||
|
|
||||||
|
LABEL maintainer="otthorn@crans.org"
|
||||||
|
LABEL description="Debian Buster backports image with django and tox \
|
||||||
|
installed for testing purposes"
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends -t buster-backports -y \
|
||||||
|
python3-django python3-django-crispy-forms \
|
||||||
|
python3-django-extensions python3-django-filters \
|
||||||
|
python3-django-polymorphic \
|
||||||
|
python3-djangorestframework python3-django-oauth-toolkit \
|
||||||
|
python3-psycopg2 python3-pil \
|
||||||
|
python3-babel python3-lockfile python3-pip python3-phonenumbers \
|
||||||
|
python3-memcache \
|
||||||
|
python3-bs4 python3-setuptools tox texlive-xetex \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
22
docker_ci/Dockerfile.38
Normal file
22
docker_ci/Dockerfile.38
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
LABEL maintainer="otthorn@crans.org"
|
||||||
|
LABEL description="Ubuntu 20.04 image with django and tox \
|
||||||
|
installed for testing purposes"
|
||||||
|
|
||||||
|
# fix tzdata prompt
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends -y \
|
||||||
|
python3-django python3-django-crispy-forms \
|
||||||
|
python3-django-extensions python3-django-filters \
|
||||||
|
python3-django-polymorphic \
|
||||||
|
python3-djangorestframework python3-django-oauth-toolkit \
|
||||||
|
python3-psycopg2 python3-pil \
|
||||||
|
python3-babel python3-lockfile python3-pip python3-phonenumbers \
|
||||||
|
python3-memcache \
|
||||||
|
python3-bs4 python3-setuptools tox texlive-xetex \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
18
docker_ci/Dockerfile.39
Normal file
18
docker_ci/Dockerfile.39
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
FROM debian:bullseye
|
||||||
|
|
||||||
|
LABEL maintainer="otthorn@crans.org"
|
||||||
|
LABEL description="Debian Bulleye image with django and tox \
|
||||||
|
installed for testing purposes"
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends -y \
|
||||||
|
python3-django python3-django-crispy-forms \
|
||||||
|
python3-django-extensions python3-django-filters \
|
||||||
|
python3-django-polymorphic \
|
||||||
|
python3-djangorestframework python3-django-oauth-toolkit \
|
||||||
|
python3-psycopg2 python3-pil \
|
||||||
|
python3-babel python3-lockfile python3-pip python3-phonenumbers \
|
||||||
|
python3-memcache \
|
||||||
|
python3-bs4 python3-setuptools tox texlive-xetex \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
10
docker_ci/Dockerfile.ansiblelint
Normal file
10
docker_ci/Dockerfile.ansiblelint
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
FROM python:3.9-alpine
|
||||||
|
|
||||||
|
LABEL maintainer="otthorn@crans.org"
|
||||||
|
LABEL description="Alpine image with ansible-lint and yamllint \
|
||||||
|
installed for linting purposes"
|
||||||
|
|
||||||
|
RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl-dev cargo
|
||||||
|
RUN pip install --no-cache-dir "yamllint>=1.26.0,<2.0"
|
||||||
|
RUN pip install --no-cache-dir "ansible-lint==5.0.0"
|
||||||
|
RUN pip install --no-cache-dir "ansible>=2.10,<2.11"
|
8
docker_ci/Dockerfile.tox
Normal file
8
docker_ci/Dockerfile.tox
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
FROM alpine:3.13
|
||||||
|
|
||||||
|
LABEL maintainer="otthorn@crans.org"
|
||||||
|
LABEL description="Alpine image with tox \
|
||||||
|
installed for linting purposes"
|
||||||
|
|
||||||
|
RUN apk --no-cache add py3-pip=20.3.4-r0
|
||||||
|
RUN pip install --no-cache-dir tox==3.22.0
|
21
docker_ci/README.md
Normal file
21
docker_ci/README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Docker CI
|
||||||
|
|
||||||
|
Ce dossier contient les images docker à construire pour la CI. L'idée est
|
||||||
|
d'avoir une image pré-construire, au dessus laquel il y a besoin de faire
|
||||||
|
tourner uniquement les commandes qui nous intéresse. Cela permet notamment de
|
||||||
|
réduire drastiquement le temps que nécessite chaque test car seul la dernière
|
||||||
|
couche (layer) de l'image a besoin d'etre éxécuter.
|
||||||
|
|
||||||
|
## Build les images
|
||||||
|
|
||||||
|
Pour build les images il suffit de lancer les commandes suivantes
|
||||||
|
|
||||||
|
```
|
||||||
|
cd docker_ci/
|
||||||
|
docker build -t nk20_ci_37 -f Dockerfile.37 .
|
||||||
|
docker build -t nk20_ci_38 -f Dockerfile.38 .
|
||||||
|
docker build -t nk20_ci_39 -f Dockerfile.39 .
|
||||||
|
```
|
||||||
|
|
||||||
|
Elles sont acutellement build et disponible sur dockerhub
|
||||||
|
https://hub.docker.com/otthorn/nk20_ci_37
|
@ -142,3 +142,17 @@ class TurbolinksMiddleware(object):
|
|||||||
location = request.session.pop('_turbolinks_redirect_to')
|
location = request.session.pop('_turbolinks_redirect_to')
|
||||||
response['Turbolinks-Location'] = location
|
response['Turbolinks-Location'] = location
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class ClacksMiddleware(object):
|
||||||
|
"""
|
||||||
|
Add Clacks Overhead header on each response.
|
||||||
|
See https://www.gnuterrypratchett.com/
|
||||||
|
"""
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.get_response(request)
|
||||||
|
response['X-Clacks-Overhead'] = 'GNU Terry Pratchett'
|
||||||
|
return response
|
||||||
|
@ -83,6 +83,7 @@ MIDDLEWARE = [
|
|||||||
'note_kfet.middlewares.SessionMiddleware',
|
'note_kfet.middlewares.SessionMiddleware',
|
||||||
'note_kfet.middlewares.LoginByIPMiddleware',
|
'note_kfet.middlewares.LoginByIPMiddleware',
|
||||||
'note_kfet.middlewares.TurbolinksMiddleware',
|
'note_kfet.middlewares.TurbolinksMiddleware',
|
||||||
|
'note_kfet.middlewares.ClacksMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'note_kfet.urls'
|
ROOT_URLCONF = 'note_kfet.urls'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* You should never see this file.
|
* If you are not using the main language, you should never see this file.
|
||||||
* It is here only for compatibility reasons in case of the command `compilejsmessages` was never executed.
|
* It is here only for compatibility reasons in case of the command `compilejsmessages` was never executed.
|
||||||
* Please execute this command to generate translation strings.
|
* Please execute this command to generate translation strings.
|
||||||
*/
|
*/
|
||||||
@ -9,14 +9,7 @@
|
|||||||
var django = globals.django || (globals.django = {});
|
var django = globals.django || (globals.django = {});
|
||||||
|
|
||||||
|
|
||||||
django.pluralidx = function(n) {
|
django.pluralidx = function(count) { return (count == 1) ? 0 : 1; };
|
||||||
var v=(n != 1);
|
|
||||||
if (typeof(v) == 'boolean') {
|
|
||||||
return v ? 1 : 0;
|
|
||||||
} else {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/* gettext library */
|
/* gettext library */
|
||||||
@ -73,39 +66,41 @@
|
|||||||
/* formatting library */
|
/* formatting library */
|
||||||
|
|
||||||
django.formats = {
|
django.formats = {
|
||||||
"DATETIME_FORMAT": "j \\d\\e F \\d\\e Y \\a \\l\\a\\s H:i",
|
"DATETIME_FORMAT": "N j, Y, P",
|
||||||
"DATETIME_INPUT_FORMATS": [
|
"DATETIME_INPUT_FORMATS": [
|
||||||
"%d/%m/%Y %H:%M:%S",
|
|
||||||
"%d/%m/%Y %H:%M:%S.%f",
|
|
||||||
"%d/%m/%Y %H:%M",
|
|
||||||
"%d/%m/%y %H:%M:%S",
|
|
||||||
"%d/%m/%y %H:%M:%S.%f",
|
|
||||||
"%d/%m/%y %H:%M",
|
|
||||||
"%Y-%m-%d %H:%M:%S",
|
"%Y-%m-%d %H:%M:%S",
|
||||||
"%Y-%m-%d %H:%M:%S.%f",
|
"%Y-%m-%d %H:%M:%S.%f",
|
||||||
"%Y-%m-%d %H:%M",
|
"%Y-%m-%d %H:%M",
|
||||||
"%Y-%m-%d"
|
"%Y-%m-%d",
|
||||||
|
"%m/%d/%Y %H:%M:%S",
|
||||||
|
"%m/%d/%Y %H:%M:%S.%f",
|
||||||
|
"%m/%d/%Y %H:%M",
|
||||||
|
"%m/%d/%Y",
|
||||||
|
"%m/%d/%y %H:%M:%S",
|
||||||
|
"%m/%d/%y %H:%M:%S.%f",
|
||||||
|
"%m/%d/%y %H:%M",
|
||||||
|
"%m/%d/%y"
|
||||||
],
|
],
|
||||||
"DATE_FORMAT": "j \\d\\e F \\d\\e Y",
|
"DATE_FORMAT": "N j, Y",
|
||||||
"DATE_INPUT_FORMATS": [
|
"DATE_INPUT_FORMATS": [
|
||||||
"%d/%m/%Y",
|
"%Y-%m-%d",
|
||||||
"%d/%m/%y",
|
"%m/%d/%Y",
|
||||||
"%Y-%m-%d"
|
"%m/%d/%y"
|
||||||
],
|
],
|
||||||
"DECIMAL_SEPARATOR": ",",
|
"DECIMAL_SEPARATOR": ".",
|
||||||
"FIRST_DAY_OF_WEEK": 1,
|
"FIRST_DAY_OF_WEEK": 0,
|
||||||
"MONTH_DAY_FORMAT": "j \\d\\e F",
|
"MONTH_DAY_FORMAT": "F j",
|
||||||
"NUMBER_GROUPING": 3,
|
"NUMBER_GROUPING": 3,
|
||||||
"SHORT_DATETIME_FORMAT": "d/m/Y H:i",
|
"SHORT_DATETIME_FORMAT": "m/d/Y P",
|
||||||
"SHORT_DATE_FORMAT": "d/m/Y",
|
"SHORT_DATE_FORMAT": "m/d/Y",
|
||||||
"THOUSAND_SEPARATOR": ".",
|
"THOUSAND_SEPARATOR": ",",
|
||||||
"TIME_FORMAT": "H:i",
|
"TIME_FORMAT": "P",
|
||||||
"TIME_INPUT_FORMATS": [
|
"TIME_INPUT_FORMATS": [
|
||||||
"%H:%M:%S",
|
"%H:%M:%S",
|
||||||
"%H:%M:%S.%f",
|
"%H:%M:%S.%f",
|
||||||
"%H:%M"
|
"%H:%M"
|
||||||
],
|
],
|
||||||
"YEAR_MONTH_FORMAT": "F \\d\\e Y"
|
"YEAR_MONTH_FORMAT": "F Y"
|
||||||
};
|
};
|
||||||
|
|
||||||
django.get_format = function(format_type) {
|
django.get_format = function(format_type) {
|
||||||
|
134
note_kfet/static/js/jsi18n/en.js
Normal file
134
note_kfet/static/js/jsi18n/en.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* You should never see this file.
|
||||||
|
* It is here only for compatibility reasons in case of the command `compilejsmessages` was never executed.
|
||||||
|
* Please execute this command to generate translation strings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(globals) {
|
||||||
|
|
||||||
|
var django = globals.django || (globals.django = {});
|
||||||
|
|
||||||
|
|
||||||
|
django.pluralidx = function(n) {
|
||||||
|
var v=(n != 1);
|
||||||
|
if (typeof(v) == 'boolean') {
|
||||||
|
return v ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* gettext library */
|
||||||
|
|
||||||
|
django.catalog = django.catalog || {};
|
||||||
|
|
||||||
|
|
||||||
|
if (!django.jsi18n_initialized) {
|
||||||
|
django.gettext = function(msgid) {
|
||||||
|
var value = django.catalog[msgid];
|
||||||
|
if (typeof(value) == 'undefined') {
|
||||||
|
return msgid;
|
||||||
|
} else {
|
||||||
|
return (typeof(value) == 'string') ? value : value[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
django.ngettext = function(singular, plural, count) {
|
||||||
|
var value = django.catalog[singular];
|
||||||
|
if (typeof(value) == 'undefined') {
|
||||||
|
return (count == 1) ? singular : plural;
|
||||||
|
} else {
|
||||||
|
return value.constructor === Array ? value[django.pluralidx(count)] : value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
django.gettext_noop = function(msgid) { return msgid; };
|
||||||
|
|
||||||
|
django.pgettext = function(context, msgid) {
|
||||||
|
var value = django.gettext(context + '\x04' + msgid);
|
||||||
|
if (value.indexOf('\x04') != -1) {
|
||||||
|
value = msgid;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
django.npgettext = function(context, singular, plural, count) {
|
||||||
|
var value = django.ngettext(context + '\x04' + singular, context + '\x04' + plural, count);
|
||||||
|
if (value.indexOf('\x04') != -1) {
|
||||||
|
value = django.ngettext(singular, plural, count);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
django.interpolate = function(fmt, obj, named) {
|
||||||
|
if (named) {
|
||||||
|
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
|
||||||
|
} else {
|
||||||
|
return fmt.replace(/%s/g, function(match){return String(obj.shift())});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* formatting library */
|
||||||
|
|
||||||
|
django.formats = {
|
||||||
|
"DATETIME_FORMAT": "j \\d\\e F \\d\\e Y \\a \\l\\a\\s H:i",
|
||||||
|
"DATETIME_INPUT_FORMATS": [
|
||||||
|
"%d/%m/%Y %H:%M:%S",
|
||||||
|
"%d/%m/%Y %H:%M:%S.%f",
|
||||||
|
"%d/%m/%Y %H:%M",
|
||||||
|
"%d/%m/%y %H:%M:%S",
|
||||||
|
"%d/%m/%y %H:%M:%S.%f",
|
||||||
|
"%d/%m/%y %H:%M",
|
||||||
|
"%Y-%m-%d %H:%M:%S",
|
||||||
|
"%Y-%m-%d %H:%M:%S.%f",
|
||||||
|
"%Y-%m-%d %H:%M",
|
||||||
|
"%Y-%m-%d"
|
||||||
|
],
|
||||||
|
"DATE_FORMAT": "j \\d\\e F \\d\\e Y",
|
||||||
|
"DATE_INPUT_FORMATS": [
|
||||||
|
"%d/%m/%Y",
|
||||||
|
"%d/%m/%y",
|
||||||
|
"%Y-%m-%d"
|
||||||
|
],
|
||||||
|
"DECIMAL_SEPARATOR": ",",
|
||||||
|
"FIRST_DAY_OF_WEEK": 1,
|
||||||
|
"MONTH_DAY_FORMAT": "j \\d\\e F",
|
||||||
|
"NUMBER_GROUPING": 3,
|
||||||
|
"SHORT_DATETIME_FORMAT": "d/m/Y H:i",
|
||||||
|
"SHORT_DATE_FORMAT": "d/m/Y",
|
||||||
|
"THOUSAND_SEPARATOR": ".",
|
||||||
|
"TIME_FORMAT": "H:i",
|
||||||
|
"TIME_INPUT_FORMATS": [
|
||||||
|
"%H:%M:%S",
|
||||||
|
"%H:%M:%S.%f",
|
||||||
|
"%H:%M"
|
||||||
|
],
|
||||||
|
"YEAR_MONTH_FORMAT": "F \\d\\e Y"
|
||||||
|
};
|
||||||
|
|
||||||
|
django.get_format = function(format_type) {
|
||||||
|
var value = django.formats[format_type];
|
||||||
|
if (typeof(value) == 'undefined') {
|
||||||
|
return format_type;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* add to global namespace */
|
||||||
|
globals.pluralidx = django.pluralidx;
|
||||||
|
globals.gettext = django.gettext;
|
||||||
|
globals.ngettext = django.ngettext;
|
||||||
|
globals.gettext_noop = django.gettext_noop;
|
||||||
|
globals.pgettext = django.pgettext;
|
||||||
|
globals.npgettext = django.npgettext;
|
||||||
|
globals.interpolate = django.interpolate;
|
||||||
|
globals.get_format = django.get_format;
|
||||||
|
|
||||||
|
django.jsi18n_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}(this));
|
||||||
|
|
@ -39,7 +39,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<script src="{% static "js/konami.js" %}"></script>
|
<script src="{% static "js/konami.js" %}"></script>
|
||||||
|
|
||||||
{# Translation in javascript files #}
|
{# Translation in javascript files #}
|
||||||
<script src="{% static "js/jsi18n/jsi18n."|add:LANGUAGE_CODE|add:".js" %}"></script>
|
<script src="{% static "js/jsi18n/"|add:LANGUAGE_CODE|add:".js" %}"></script>
|
||||||
|
|
||||||
{# If extra ressources are needed for a form, load here #}
|
{# If extra ressources are needed for a form, load here #}
|
||||||
{% if form.media %}
|
{% if form.media %}
|
||||||
|
Reference in New Issue
Block a user