Compare commits
220 Commits
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 | |||
9d8f47115c
|
|||
f4156f1b94
|
|||
e60994e065
|
|||
801f711994
|
|||
e4568b410f
|
|||
c8f7986d5a | |||
d3a9c442a5
|
|||
016ab5a9c9
|
|||
7866ab7ec0
|
|||
f570ff3cd5
|
|||
6b2638c271
|
|||
5cb4183e9f
|
|||
3a20555663
|
|||
95be0042e9
|
|||
48880e7fd3
|
|||
e0030771e4
|
|||
d47799e6ee
|
|||
eae091625a
|
|||
aceb77ffb9
|
|||
338c94ed05
|
|||
290848f904 | |||
72dca54bbf | |||
117d9da3ba | |||
37efebe85b | |||
3af2ec71b6 | |||
0b4a95525b | |||
af664e481f | |||
0171f16311 | |||
296b94d237 | |||
4942553335 | |||
c1efb87180 | |||
72eead8595 | |||
ade7e583e5 | |||
4a8a101822 | |||
dd2cfa6327 | |||
2adf84b7fc | |||
2f54e64ea2 | |||
8434c0062c | |||
6d976f32bf | |||
b9d49d53f2 | |||
23243e09bb | |||
2682e9a610 | |||
5635598bbc | |||
b58a0c43cd | |||
e1f647bd02 | |||
39fd3a2471 | |||
1072e227b8 | |||
cbf7e6fe6c | |||
950922d041 | |||
78fe070cd3 | |||
51d5733578 | |||
7bd895c1df | |||
e5e94c52f2 | |||
051591cb7a | |||
0e7390b669 | |||
fe4363b83d | |||
6e80016b38 | |||
08e50ffc22 | |||
9cb65277f3 | |||
224a0fdd8c | |||
6dc7604e90 | |||
cb7f3c9f18 | |||
f910feca9e | |||
91f784872c | |||
b655135a42 | |||
58aa4983e3 | |||
6cc3cf4174 | |||
2097e67321 | |||
d773303d18 | |||
3cabcf40e7 | |||
bf29efda0a | |||
ceccba0d71 | |||
3eced33082 | |||
acb3fb4a91 | |||
1c5e951c2f | |||
beb1853aef | |||
0078eb8f90 | |||
e5e758f9d9 | |||
4a78328717 | |||
65a2e8c08c | |||
b5fa428bad | |||
fb72385773 | |||
2f68601e8b | |||
0b1bed8048 | |||
8ada0e51f2 | |||
c3d613947f | |||
36b8157372 | |||
992cfe8e23 | |||
18a8ff1b8a | |||
c61bb2e90d | |||
4b12e3ed08 | |||
af07ed9807 | |||
bbe53b3b63 | |||
536f0ec226 | |||
541ed59f40 | |||
e172b4f4bb | |||
d666179037 | |||
f22e92132c | |||
ca7ad05746 | |||
f55ca2f725 | |||
d4e4ed580f | |||
8756751344 | |||
fd83fe19bf | |||
a00d95608b | |||
3303edd01f | |||
e48ef92137 | |||
919d0b7e85 | |||
439bf35b62 | |||
74b26335d1 | |||
3d733ed6af | |||
d54ab94ceb | |||
4f188ca3e5 | |||
72bac75fbd | |||
6d54aae614 | |||
8052152ea5 | |||
70448db8e5 | |||
ac2d1e8111 | |||
3ba61385a3 | |||
7353348d7a | |||
f63e2e088e | |||
420a24ebac | |||
d566def706 | |||
eaf6769e8b | |||
a61ec81cff | |||
60f2a73cc5 | |||
bcd96b2ed8 | |||
5c702187e5 | |||
905d65371f | |||
180cd3e1ec | |||
73ca65aa91 | |||
5ed0560953 | |||
dbc6fbbf71 | |||
872fd8f86d | |||
f89234b69a | |||
36a980555b | |||
826cd4d87f | |||
e8005a6c58 | |||
2270a0aa82 | |||
0f53ac45f7 | |||
670556c59e | |||
5b02ba48e0 | |||
f3f18bc25e | |||
03124e124c | |||
6308964e93 | |||
ed79097288 | |||
d7eaef8cee | |||
01d405e54b | |||
80e3cba4c6 | |||
f190053e84 | |||
218960adb5 | |||
88a1eae631 | |||
2a2ecb2acc | |||
f5486bdb63 | |||
9b090a145c | |||
860c7b50e5 | |||
afdc75c0bd | |||
c6603e8aa7 | |||
72cc1638e6 | |||
6a0dc4cb10 | |||
0f1f3b9560 | |||
c720e5483e | |||
0fd3e9db78 | |||
c34296c923 | |||
ce4c22a4a1 | |||
3e0f665ef8 | |||
be8751c815 | |||
8225445c3e | |||
f333e6a875 | |||
e5835b46a5 | |||
fe937405a6 | |||
0741c8ad2b | |||
3191dba31f | |||
428de69d93 | |||
0888afe439 | |||
3111c30e56 |
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
@ -47,3 +47,8 @@ backups/
|
||||
env/
|
||||
venv/
|
||||
db.sqlite3
|
||||
|
||||
# ansibles customs host
|
||||
ansible/host_vars/*.yaml
|
||||
!ansible/host_vars/bde*
|
||||
ansible/hosts
|
||||
|
@ -1,6 +1,7 @@
|
||||
stages:
|
||||
- test
|
||||
- quality-assurance
|
||||
- docs
|
||||
|
||||
# Also fetch submodules
|
||||
variables:
|
||||
@ -9,35 +10,22 @@ variables:
|
||||
# Debian Buster
|
||||
py37-django22:
|
||||
stage: test
|
||||
image: debian:buster-backports
|
||||
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-cas-server python3-psycopg2 python3-pil
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers
|
||||
python3-bs4 python3-setuptools tox texlive-xetex
|
||||
image: otthorn/nk20_ci_37
|
||||
script: tox -e py37-django22
|
||||
|
||||
# Ubuntu 20.04
|
||||
py38-django22:
|
||||
stage: test
|
||||
image: ubuntu:20.04
|
||||
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-cas-server python3-psycopg2 python3-pil
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers
|
||||
python3-bs4 python3-setuptools tox texlive-xetex
|
||||
image: otthorn/nk20_ci_38
|
||||
script: tox -e py38-django22
|
||||
|
||||
# Debian Bullseye
|
||||
py39-django22:
|
||||
stage: test
|
||||
image: otthorn/nk20_ci_39
|
||||
script: tox -e py39-django22
|
||||
|
||||
# Tox linter
|
||||
linters:
|
||||
stage: quality-assurance
|
||||
image: debian:buster-backports
|
||||
@ -47,3 +35,31 @@ linters:
|
||||
|
||||
# Be nice to new contributors, but please use `tox`
|
||||
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
|
||||
documentation:
|
||||
stage: docs
|
||||
image: sphinxdoc/sphinx
|
||||
before_script:
|
||||
- pip install sphinx-rtd-theme
|
||||
- cd docs
|
||||
script:
|
||||
- make dirhtml
|
||||
artifacts:
|
||||
paths:
|
||||
- docs/_build
|
||||
expire_in: 1 day
|
||||
|
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)
|
@ -8,8 +8,8 @@ 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-cas-server python3-psycopg2 python3-pil \
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers ipython3 \
|
||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil \
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||
python3-bs4 python3-setuptools \
|
||||
uwsgi uwsgi-plugin-python3 \
|
||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome && \
|
||||
|
38
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 !
|
||||
|
||||
## 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.**
|
||||
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**.
|
||||
|
||||
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.
|
||||
|
||||
0. Sous Debian Buster, **activer Debian Backports.** En effet Django 2.2 LTS n'est que disponible dans les backports.
|
||||
@ -93,10 +111,10 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
||||
$ sudo apt 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-cas-server python3-psycopg2 python3-pil \
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers ipython3 \
|
||||
python3-bs4 python3-setuptools \
|
||||
uwsgi uwsgi-plugin-python3 \
|
||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil \
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||
python3-bs4 python3-setuptools python3-docutils \
|
||||
memcached uwsgi uwsgi-plugin-python3 \
|
||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome \
|
||||
nginx python3-venv git acl
|
||||
```
|
||||
@ -267,14 +285,18 @@ La documentation plus haut niveau sur le développement est disponible sur [le W
|
||||
|
||||
### Regénérer les fichiers de traduction
|
||||
|
||||
Pour regénérer les traductions vous pouvez vous placer à la racine du projet et lancer le script `makemessages`. Il faut penser à ignorer les dossiers ne contenant pas notre code, dont le virtualenv.
|
||||
Pour regénérer les traductions vous pouvez vous placer à la racine du projet et lancer le script `makemessages`.
|
||||
Il faut penser à ignorer les dossiers ne contenant pas notre code, dont le virtualenv.
|
||||
De plus, il faut aussi extraire les variables des fichiers JavaScript.
|
||||
|
||||
```bash
|
||||
django-admin makemessages -i env
|
||||
python3 manage.py makemessages -i env
|
||||
python3 manage.py makemessages -i env -e js -d djangojs
|
||||
```
|
||||
|
||||
Une fois les fichiers édités, vous pouvez compiler les nouvelles traductions avec
|
||||
|
||||
```bash
|
||||
django-admin compilemessages
|
||||
python3 manage.py compilemessages
|
||||
python3 manage.py compilejsmessages
|
||||
```
|
||||
|
@ -7,7 +7,7 @@
|
||||
prompt: "Password of the database (leave it blank to skip database init)"
|
||||
private: yes
|
||||
vars:
|
||||
mirror: deb.debian.org
|
||||
mirror: mirror.crans.org
|
||||
roles:
|
||||
- 1-apt-basic
|
||||
- 2-nk20
|
||||
@ -16,3 +16,4 @@
|
||||
- 5-nginx
|
||||
- 6-psql
|
||||
- 7-postinstall
|
||||
- 8-docs
|
||||
|
@ -3,3 +3,4 @@ note:
|
||||
server_name: note-beta.crans.org
|
||||
git_branch: beta
|
||||
cron_enabled: false
|
||||
email: notekfet2020@lists.crans.org
|
||||
|
@ -3,3 +3,4 @@ note:
|
||||
server_name: note-dev.crans.org
|
||||
git_branch: beta
|
||||
cron_enabled: false
|
||||
email: notekfet2020@lists.crans.org
|
@ -3,3 +3,4 @@ note:
|
||||
server_name: note.crans.org
|
||||
git_branch: master
|
||||
cron_enabled: true
|
||||
email: notekfet2020@lists.crans.org
|
||||
|
@ -1,5 +1,5 @@
|
||||
[dev]
|
||||
bde3-virt.adh.crans.org
|
||||
bde-note-dev.adh.crans.org
|
||||
bde-nk20-beta.adh.crans.org
|
||||
|
||||
[prod]
|
@ -3,11 +3,12 @@
|
||||
apt_repository:
|
||||
repo: deb http://{{ mirror }}/debian buster-backports main
|
||||
state: present
|
||||
when: ansible_facts['distribution'] == "Debian"
|
||||
|
||||
- name: Install note_kfet APT dependencies
|
||||
apt:
|
||||
update_cache: true
|
||||
default_release: buster-backports
|
||||
default_release: "{{ 'buster-backports' if ansible_facts['distribution'] == 'Debian' }}"
|
||||
install_recommends: false
|
||||
name:
|
||||
# Common tools
|
||||
@ -23,13 +24,14 @@
|
||||
- python3-babel
|
||||
- python3-bs4
|
||||
- python3-django
|
||||
- python3-django-cas-server
|
||||
- python3-django-crispy-forms
|
||||
- python3-django-extensions
|
||||
- python3-django-filters
|
||||
- python3-django-oauth-toolkit
|
||||
- python3-django-polymorphic
|
||||
- python3-djangorestframework
|
||||
- python3-lockfile
|
||||
- python3-memcache
|
||||
- python3-phonenumbers
|
||||
- python3-pil
|
||||
- python3-pip
|
||||
@ -40,6 +42,9 @@
|
||||
# LaTeX (PDF generation)
|
||||
- texlive-xetex
|
||||
|
||||
# Cache server
|
||||
- memcached
|
||||
|
||||
# WSGI server
|
||||
- uwsgi
|
||||
- uwsgi-plugin-python3
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
- name: Use default env vars (should be updated!)
|
||||
template:
|
||||
src: "env_example"
|
||||
src: "env.j2"
|
||||
dest: "/var/www/note_kfet/.env"
|
||||
mode: 0644
|
||||
force: false
|
||||
@ -36,3 +36,13 @@
|
||||
dest: /etc/cron.d/note
|
||||
owner: 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
@ -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
|
||||
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
|
||||
file:
|
||||
path: /etc/letsencrypt/conf.d
|
||||
@ -19,3 +24,17 @@
|
||||
src: "letsencrypt/conf.d/nk20.ini.j2"
|
||||
dest: "/etc/letsencrypt/conf.d/nk20.ini"
|
||||
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
|
||||
|
||||
# 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
|
||||
text = True
|
||||
|
@ -1,5 +1,5 @@
|
||||
# the upstream component nginx needs to connect to
|
||||
upstream note{
|
||||
upstream note {
|
||||
server unix:///var/www/note_kfet/note_kfet.sock; # file socket
|
||||
}
|
||||
|
||||
@ -50,6 +50,10 @@ server {
|
||||
alias /var/www/note_kfet/static; # your Django project's static files - amend as required
|
||||
}
|
||||
|
||||
location /doc {
|
||||
alias /var/www/documentation; # The documentation of the project
|
||||
}
|
||||
|
||||
# Finally, send all non-media requests to the Django server.
|
||||
location / {
|
||||
uwsgi_pass note;
|
||||
|
@ -11,14 +11,14 @@
|
||||
until: pkg_result is succeeded
|
||||
|
||||
- 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:
|
||||
name: note
|
||||
password: "{{ DB_PASSWORD }}"
|
||||
become_user: postgres
|
||||
|
||||
- name: Create NK20 database
|
||||
when: "DB_PASSWORD|bool"
|
||||
when: DB_PASSWORD|length >0
|
||||
postgresql_db:
|
||||
name: note_db
|
||||
owner: note
|
||||
|
@ -1,4 +1,10 @@
|
||||
---
|
||||
- name: Collect static files
|
||||
command: /var/www/note_kfet/env/bin/python manage.py collectstatic --noinput
|
||||
args:
|
||||
chdir: /var/www/note_kfet
|
||||
become_user: www-data
|
||||
|
||||
- name: Migrate Django database
|
||||
command: /var/www/note_kfet/env/bin/python manage.py migrate
|
||||
args:
|
||||
@ -11,14 +17,14 @@
|
||||
chdir: /var/www/note_kfet
|
||||
become_user: www-data
|
||||
|
||||
- name: Compile JavaScript messages
|
||||
command: /var/www/note_kfet/env/bin/python manage.py compilejsmessages
|
||||
args:
|
||||
chdir: /var/www/note_kfet
|
||||
become_user: www-data
|
||||
|
||||
- name: Install initial fixtures
|
||||
command: /var/www/note_kfet/env/bin/python manage.py loaddata initial
|
||||
args:
|
||||
chdir: /var/www/note_kfet
|
||||
become_user: postgres
|
||||
|
||||
- name: Collect static files
|
||||
command: /var/www/note_kfet/env/bin/python manage.py collectstatic --noinput
|
||||
args:
|
||||
chdir: /var/www/note_kfet
|
||||
become_user: www-data
|
||||
|
20
ansible/roles/8-docs/tasks/main.yml
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: Install Sphinx and RTD theme
|
||||
pip:
|
||||
requirements: /var/www/note_kfet/docs/requirements.txt
|
||||
virtualenv: /var/www/note_kfet/env
|
||||
virtualenv_command: /usr/bin/python3 -m venv
|
||||
virtualenv_site_packages: true
|
||||
become_user: www-data
|
||||
|
||||
- name: Create documentation directory with good permissions
|
||||
file:
|
||||
path: /var/www/documentation
|
||||
state: directory
|
||||
owner: www-data
|
||||
group: www-data
|
||||
mode: u=rwx,g=rwxs,o=rx
|
||||
|
||||
- name: Build HTML documentation
|
||||
command: /var/www/note_kfet/env/bin/sphinx-build -b dirhtml /var/www/note_kfet/docs/ /var/www/documentation/
|
||||
become_user: www-data
|
@ -15,10 +15,10 @@ class ActivityTypeViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `ActivityType` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/activity/type/
|
||||
"""
|
||||
queryset = ActivityType.objects.all()
|
||||
queryset = ActivityType.objects.order_by('id')
|
||||
serializer_class = ActivityTypeSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['name', 'can_invite', ]
|
||||
filterset_fields = ['name', 'manage_entries', 'can_invite', 'guest_entry_fee', ]
|
||||
|
||||
|
||||
class ActivityViewSet(ReadProtectedModelViewSet):
|
||||
@ -27,10 +27,16 @@ class ActivityViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Activity` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/activity/activity/
|
||||
"""
|
||||
queryset = Activity.objects.all()
|
||||
queryset = Activity.objects.order_by('id')
|
||||
serializer_class = ActivitySerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['name', 'description', 'activity_type', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'description', 'activity_type', 'location', 'creater', 'organizer', 'attendees_club',
|
||||
'date_start', 'date_end', 'valid', 'open', ]
|
||||
search_fields = ['$name', '$description', '$location', '$creater__last_name', '$creater__first_name',
|
||||
'$creater__email', '$creater__note__alias__name', '$creater__note__alias__normalized_name',
|
||||
'$organizer__name', '$organizer__email', '$organizer__note__alias__name',
|
||||
'$organizer__note__alias__normalized_name', '$attendees_club__name', '$attendees_club__email',
|
||||
'$attendees_club__note__alias__name', '$attendees_club__note__alias__normalized_name', ]
|
||||
|
||||
|
||||
class GuestViewSet(ReadProtectedModelViewSet):
|
||||
@ -39,10 +45,13 @@ class GuestViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Guest` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/activity/guest/
|
||||
"""
|
||||
queryset = Guest.objects.all()
|
||||
queryset = Guest.objects.order_by('id')
|
||||
serializer_class = GuestSerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$last_name', '$first_name', '$inviter__alias__name', '$inviter__alias__normalized_name', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['activity', 'activity__name', 'last_name', 'first_name', 'inviter', 'inviter__alias__name',
|
||||
'inviter__alias__normalized_name', ]
|
||||
search_fields = ['$activity__name', '$last_name', '$first_name', '$inviter__user__email', '$inviter__alias__name',
|
||||
'$inviter__alias__normalized_name', ]
|
||||
|
||||
|
||||
class EntryViewSet(ReadProtectedModelViewSet):
|
||||
@ -51,7 +60,9 @@ class EntryViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Entry` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/activity/entry/
|
||||
"""
|
||||
queryset = Entry.objects.all()
|
||||
queryset = Entry.objects.order_by('id')
|
||||
serializer_class = EntrySerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$last_name', '$first_name', '$inviter__alias__name', '$inviter__alias__normalized_name', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['activity', 'time', 'note', 'guest', ]
|
||||
search_fields = ['$activity__name', '$note__user__email', '$note__alias__name', '$note__alias__normalized_name',
|
||||
'$guest__last_name', '$guest__first_name', ]
|
||||
|
@ -7,7 +7,7 @@ from threading import Thread
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -123,6 +123,7 @@ class Activity(models.Model):
|
||||
verbose_name=_('open'),
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Update the activity wiki page each time the activity is updated (validation, change description, ...)
|
||||
@ -194,8 +195,8 @@ class Entry(models.Model):
|
||||
else _("Entry for {note} to the activity {activity}").format(
|
||||
guest=str(self.guest), note=str(self.note), activity=str(self.activity))
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
qs = Entry.objects.filter(~Q(pk=self.pk), activity=self.activity, note=self.note, guest=self.guest)
|
||||
if qs.exists():
|
||||
raise ValidationError(_("Already entered on ") + _("{:%Y-%m-%d %H:%M:%S}").format(qs.get().time, ))
|
||||
@ -260,6 +261,7 @@ class Guest(models.Model):
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
one_year = timedelta(days=365)
|
||||
|
||||
|
@ -30,7 +30,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
headers: {"X-CSRFTOKEN": CSRF_TOKEN}
|
||||
})
|
||||
.done(function() {
|
||||
addMsg('Invité supprimé','success');
|
||||
addMsg('{% trans "Guest deleted" %}', 'success');
|
||||
$("#guests_table").load(location.pathname + " #guests_table");
|
||||
})
|
||||
.fail(function(xhr, textStatus, error) {
|
||||
|
@ -86,10 +86,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
}).done(function () {
|
||||
if (target.hasClass("table-info"))
|
||||
addMsg(
|
||||
"Entrée effectuée, mais attention : la personne n'est plus adhérente Kfet.",
|
||||
"{% trans "Entry done, but caution: the user is not a Kfet member." %}",
|
||||
"warning", 10000);
|
||||
else
|
||||
addMsg("Entrée effectuée !", "success", 4000);
|
||||
addMsg("Entry made!", "success", 4000);
|
||||
reloadTable(true);
|
||||
}).fail(function (xhr) {
|
||||
errMsg(xhr.responseJSON, 4000);
|
||||
@ -121,10 +121,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
}).done(function () {
|
||||
if (target.hasClass("table-info"))
|
||||
addMsg(
|
||||
"Entrée effectuée, mais attention : la personne n'est plus adhérente Kfet.",
|
||||
"{% trans "Entry done, but caution: the user is not a Kfet member." %}",
|
||||
"warning", 10000);
|
||||
else
|
||||
addMsg("Entrée effectuée !", "success", 4000);
|
||||
addMsg("{% trans "Entry done!" %}", "success", 4000);
|
||||
reloadTable(true);
|
||||
}).fail(function (xhr) {
|
||||
errMsg(xhr.responseJSON, 4000);
|
||||
|
@ -3,13 +3,16 @@
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from api.tests import TestAPI
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from activity.models import Activity, ActivityType, Guest, Entry
|
||||
from member.models import Club
|
||||
|
||||
from ..api.views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet
|
||||
from ..models import Activity, ActivityType, Guest, Entry
|
||||
|
||||
|
||||
class TestActivities(TestCase):
|
||||
"""
|
||||
@ -173,3 +176,58 @@ class TestActivities(TestCase):
|
||||
"""
|
||||
response = self.client.get(reverse("activity:calendar_ics"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestActivityAPI(TestAPI):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
self.activity = Activity.objects.create(
|
||||
name="Activity",
|
||||
description="This is a test activity\non two very very long lines\nbecause this is very important.",
|
||||
location="Earth",
|
||||
activity_type=ActivityType.objects.get(name="Pot"),
|
||||
creater=self.user,
|
||||
organizer=Club.objects.get(name="Kfet"),
|
||||
attendees_club=Club.objects.get(name="Kfet"),
|
||||
date_start=timezone.now(),
|
||||
date_end=timezone.now() + timedelta(days=2),
|
||||
valid=True,
|
||||
)
|
||||
|
||||
self.guest = Guest.objects.create(
|
||||
activity=self.activity,
|
||||
inviter=self.user.note,
|
||||
last_name="GUEST",
|
||||
first_name="Guest",
|
||||
)
|
||||
|
||||
self.entry = Entry.objects.create(
|
||||
activity=self.activity,
|
||||
note=self.user.note,
|
||||
guest=self.guest,
|
||||
)
|
||||
|
||||
def test_activity_api(self):
|
||||
"""
|
||||
Load Activity API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(ActivityViewSet, "/api/activity/activity/")
|
||||
|
||||
def test_activity_type_api(self):
|
||||
"""
|
||||
Load ActivityType API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(ActivityTypeViewSet, "/api/activity/type/")
|
||||
|
||||
def test_entry_api(self):
|
||||
"""
|
||||
Load Entry API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(EntryViewSet, "/api/activity/entry/")
|
||||
|
||||
def test_guest_api(self):
|
||||
"""
|
||||
Load Guest API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(GuestViewSet, "/api/activity/guest/")
|
||||
|
@ -7,12 +7,15 @@ from django.conf import settings
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.db.models import F, Q
|
||||
from django.http import HttpResponse
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.generic import DetailView, TemplateView, UpdateView
|
||||
from django_tables2.views import SingleTableView
|
||||
from note.models import Alias, NoteSpecial, NoteUser
|
||||
@ -44,6 +47,7 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
date_end=timezone.now(),
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.creater = self.request.user
|
||||
return super().form_valid(form)
|
||||
@ -145,6 +149,7 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
form.fields["inviter"].initial = self.request.user.note
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.activity = Activity.objects\
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).get(pk=self.kwargs["pk"])
|
||||
@ -285,6 +290,8 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||
return context
|
||||
|
||||
|
||||
# Cache for 1 hour
|
||||
@method_decorator(cache_page(60 * 60), name='dispatch')
|
||||
class CalendarView(View):
|
||||
"""
|
||||
Render an ICS calendar with all valid activities.
|
||||
|
237
apps/api/tests.py
Normal file
@ -0,0 +1,237 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
from datetime import datetime, date
|
||||
from urllib.parse import quote_plus
|
||||
from warnings import warn
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models.fields.files import ImageFieldFile
|
||||
from django.test import TestCase
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from member.models import Membership, Club
|
||||
from note.models import NoteClub, NoteUser, Alias, Note
|
||||
from permission.models import PermissionMask, Permission, Role
|
||||
from phonenumbers import PhoneNumber
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
|
||||
from .viewsets import ContentTypeViewSet, UserViewSet
|
||||
|
||||
|
||||
class TestAPI(TestCase):
|
||||
"""
|
||||
Load API pages and check that filters are working.
|
||||
"""
|
||||
fixtures = ('initial', )
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.user = User.objects.create_superuser(
|
||||
username="adminapi",
|
||||
password="adminapi",
|
||||
email="adminapi@example.com",
|
||||
last_name="Admin",
|
||||
first_name="Admin",
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
sess = self.client.session
|
||||
sess["permission_mask"] = 42
|
||||
sess.save()
|
||||
|
||||
def check_viewset(self, viewset, url):
|
||||
"""
|
||||
This function should be called inside a unit test.
|
||||
This loads the viewset and for each filter entry, it checks that the filter is running good.
|
||||
"""
|
||||
resp = self.client.get(url + "?format=json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
model = viewset.serializer_class.Meta.model
|
||||
|
||||
if not model.objects.exists(): # pragma: no cover
|
||||
warn(f"Warning: unable to test API filters for the model {model._meta.verbose_name} "
|
||||
"since there is no instance of it.")
|
||||
return
|
||||
|
||||
if hasattr(viewset, "filter_backends"):
|
||||
backends = viewset.filter_backends
|
||||
obj = model.objects.last()
|
||||
|
||||
if DjangoFilterBackend in backends:
|
||||
# Specific search
|
||||
for field in viewset.filterset_fields:
|
||||
obj = self.fix_note_object(obj, field)
|
||||
|
||||
value = self.get_value(obj, field)
|
||||
if value is None: # pragma: no cover
|
||||
warn(f"Warning: the filter {field} for the model {model._meta.verbose_name} "
|
||||
"has not been tested.")
|
||||
continue
|
||||
resp = self.client.get(url + f"?format=json&{field}={quote_plus(str(value))}")
|
||||
self.assertEqual(resp.status_code, 200, f"The filter {field} for the model "
|
||||
f"{model._meta.verbose_name} does not work. "
|
||||
f"Given parameter: {value}")
|
||||
content = json.loads(resp.content)
|
||||
self.assertGreater(content["count"], 0, f"The filter {field} for the model "
|
||||
f"{model._meta.verbose_name} does not work. "
|
||||
f"Given parameter: {value}")
|
||||
|
||||
if OrderingFilter in backends:
|
||||
# Ensure that ordering is working well
|
||||
for field in viewset.ordering_fields:
|
||||
resp = self.client.get(url + f"?ordering={field}")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
resp = self.client.get(url + f"?ordering=-{field}")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
if SearchFilter in backends:
|
||||
# Basic search
|
||||
for field in viewset.search_fields:
|
||||
obj = self.fix_note_object(obj, field)
|
||||
|
||||
if field[0] == '$' or field[0] == '=':
|
||||
field = field[1:]
|
||||
value = self.get_value(obj, field)
|
||||
if value is None: # pragma: no cover
|
||||
warn(f"Warning: the filter {field} for the model {model._meta.verbose_name} "
|
||||
"has not been tested.")
|
||||
continue
|
||||
resp = self.client.get(url + f"?format=json&search={quote_plus(str(value))}")
|
||||
self.assertEqual(resp.status_code, 200, f"The filter {field} for the model "
|
||||
f"{model._meta.verbose_name} does not work. "
|
||||
f"Given parameter: {value}")
|
||||
content = json.loads(resp.content)
|
||||
self.assertGreater(content["count"], 0, f"The filter {field} for the model "
|
||||
f"{model._meta.verbose_name} does not work. "
|
||||
f"Given parameter: {value}")
|
||||
|
||||
self.check_permissions(url, obj)
|
||||
|
||||
def check_permissions(self, url, obj):
|
||||
"""
|
||||
Check that permissions are working
|
||||
"""
|
||||
# Drop rights
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
sess = self.client.session
|
||||
sess["permission_mask"] = 0
|
||||
sess.save()
|
||||
|
||||
# Delete user permissions
|
||||
for m in Membership.objects.filter(user=self.user).all():
|
||||
m.roles.clear()
|
||||
m.save()
|
||||
|
||||
# Create a new role, which will have the checking permission
|
||||
role = Role.objects.get_or_create(name="β-tester")[0]
|
||||
role.permissions.clear()
|
||||
role.save()
|
||||
membership = Membership.objects.get_or_create(user=self.user, club=Club.objects.get(name="BDE"))[0]
|
||||
membership.roles.set([role])
|
||||
membership.save()
|
||||
|
||||
# Ensure that the access to the object is forbidden without permission
|
||||
resp = self.client.get(url + f"{obj.pk}/")
|
||||
self.assertEqual(resp.status_code, 404, f"Mysterious access to {url}{obj.pk}/ for {obj}")
|
||||
|
||||
obj.refresh_from_db()
|
||||
|
||||
# There are problems with polymorphism
|
||||
if isinstance(obj, Note) and hasattr(obj, "note_ptr"):
|
||||
obj = obj.note_ptr
|
||||
|
||||
mask = PermissionMask.objects.get(rank=0)
|
||||
|
||||
for field in obj._meta.fields:
|
||||
# Build permission query
|
||||
value = self.get_value(obj, field.name)
|
||||
if isinstance(value, date) or isinstance(value, datetime):
|
||||
value = value.isoformat()
|
||||
elif isinstance(value, ImageFieldFile):
|
||||
value = value.name
|
||||
query = json.dumps({field.name: value})
|
||||
|
||||
# Create sample permission
|
||||
permission = Permission.objects.get_or_create(
|
||||
model=ContentType.objects.get_for_model(obj._meta.model),
|
||||
query=query,
|
||||
mask=mask,
|
||||
type="view",
|
||||
permanent=False,
|
||||
description=f"Can view {obj._meta.verbose_name}",
|
||||
)[0]
|
||||
role.permissions.set([permission])
|
||||
role.save()
|
||||
|
||||
# Check that the access is possible
|
||||
resp = self.client.get(url + f"{obj.pk}/")
|
||||
self.assertEqual(resp.status_code, 200, f"Permission {permission.query} is not working "
|
||||
f"for the model {obj._meta.verbose_name}")
|
||||
|
||||
# Restore rights
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
sess = self.client.session
|
||||
sess["permission_mask"] = 42
|
||||
sess.save()
|
||||
|
||||
@staticmethod
|
||||
def get_value(obj, key: str):
|
||||
"""
|
||||
Resolve the queryset filter to get the Python value of an object.
|
||||
"""
|
||||
if hasattr(obj, "all"):
|
||||
# obj is a RelatedManager
|
||||
obj = obj.last()
|
||||
|
||||
if obj is None: # pragma: no cover
|
||||
return None
|
||||
|
||||
if '__' not in key:
|
||||
obj = getattr(obj, key)
|
||||
if hasattr(obj, "pk"):
|
||||
return obj.pk
|
||||
elif hasattr(obj, "all"):
|
||||
if not obj.exists(): # pragma: no cover
|
||||
return None
|
||||
return obj.last().pk
|
||||
elif isinstance(obj, bool):
|
||||
return int(obj)
|
||||
elif isinstance(obj, datetime):
|
||||
return obj.isoformat()
|
||||
elif isinstance(obj, PhoneNumber):
|
||||
return obj.raw_input
|
||||
return obj
|
||||
|
||||
key, remaining = key.split('__', 1)
|
||||
return TestAPI.get_value(getattr(obj, key), remaining)
|
||||
|
||||
@staticmethod
|
||||
def fix_note_object(obj, field):
|
||||
"""
|
||||
When querying an object that has a noteclub or a noteuser field,
|
||||
ensure that the object has a good value.
|
||||
"""
|
||||
if isinstance(obj, Alias):
|
||||
if "noteuser" in field:
|
||||
return NoteUser.objects.last().alias.last()
|
||||
elif "noteclub" in field:
|
||||
return NoteClub.objects.last().alias.last()
|
||||
elif isinstance(obj, Note):
|
||||
if "noteuser" in field:
|
||||
return NoteUser.objects.last()
|
||||
elif "noteclub" in field:
|
||||
return NoteClub.objects.last()
|
||||
return obj
|
||||
|
||||
|
||||
class TestBasicAPI(TestAPI):
|
||||
def test_user_api(self):
|
||||
"""
|
||||
Load the user page.
|
||||
"""
|
||||
self.check_viewset(ContentTypeViewSet, "/api/models/")
|
||||
self.check_viewset(UserViewSet, "/api/user/")
|
@ -6,6 +6,7 @@ from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework.filters import SearchFilter
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
|
||||
from permission.backends import PermissionBackend
|
||||
from note_kfet.middlewares import get_current_session
|
||||
@ -48,12 +49,13 @@ class UserViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/users/
|
||||
then render it on /api/user/
|
||||
"""
|
||||
queryset = User.objects.all()
|
||||
queryset = User.objects
|
||||
serializer_class = UserSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', ]
|
||||
filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active',
|
||||
'note__alias__name', 'note__alias__normalized_name', ]
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
@ -106,7 +108,10 @@ class ContentTypeViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/users/
|
||||
then render it on /api/models/
|
||||
"""
|
||||
queryset = ContentType.objects.all()
|
||||
queryset = ContentType.objects.order_by('id')
|
||||
serializer_class = ContentTypeSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['id', 'app_label', 'model', ]
|
||||
search_fields = ['$app_label', '$model', ]
|
||||
|
@ -15,7 +15,7 @@ class ChangelogViewSet(ReadOnlyProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Changelog` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/logs/
|
||||
"""
|
||||
queryset = Changelog.objects.all()
|
||||
queryset = Changelog.objects.order_by('id')
|
||||
serializer_class = ChangelogSerializer
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter]
|
||||
filterset_fields = ['model', 'action', "instance_pk", 'user', 'ip', ]
|
||||
|
@ -1,7 +1,8 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework.filters import SearchFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from api.viewsets import ReadProtectedModelViewSet
|
||||
|
||||
from .serializers import ProfileSerializer, ClubSerializer, MembershipSerializer
|
||||
@ -14,8 +15,15 @@ class ProfileViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Profile` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/members/profile/
|
||||
"""
|
||||
queryset = Profile.objects.all()
|
||||
queryset = Profile.objects.order_by('id')
|
||||
serializer_class = ProfileSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['user', 'user__first_name', 'user__last_name', 'user__username', 'user__email',
|
||||
'user__note__alias__name', 'user__note__alias__normalized_name', 'phone_number', "section",
|
||||
'department', 'promotion', 'address', 'paid', 'ml_events_registration', 'ml_sport_registration',
|
||||
'ml_art_registration', 'report_frequency', 'email_confirmed', 'registration_valid', ]
|
||||
search_fields = ['$user__first_name', '$user__last_name', '$user__username', '$user__email',
|
||||
'$user__note__alias__name', '$user__note__alias__normalized_name', ]
|
||||
|
||||
|
||||
class ClubViewSet(ReadProtectedModelViewSet):
|
||||
@ -24,10 +32,13 @@ class ClubViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Club` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/members/club/
|
||||
"""
|
||||
queryset = Club.objects.all()
|
||||
queryset = Club.objects.order_by('id')
|
||||
serializer_class = ClubSerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$name', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'email', 'note__alias__name', 'note__alias__normalized_name', 'parent_club',
|
||||
'parent_club__name', 'require_memberships', 'membership_fee_paid', 'membership_fee_unpaid',
|
||||
'membership_duration', 'membership_start', 'membership_end', ]
|
||||
search_fields = ['$name', '$email', '$note__alias__name', '$note__alias__normalized_name', ]
|
||||
|
||||
|
||||
class MembershipViewSet(ReadProtectedModelViewSet):
|
||||
@ -36,5 +47,14 @@ class MembershipViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Membership` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/members/membership/
|
||||
"""
|
||||
queryset = Membership.objects.all()
|
||||
queryset = Membership.objects.order_by('id')
|
||||
serializer_class = MembershipSerializer
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
filterset_fields = ['club__name', 'club__email', 'club__note__alias__name', 'club__note__alias__normalized_name',
|
||||
'user__username', 'user__last_name', 'user__first_name', 'user__email',
|
||||
'user__note__alias__name', 'user__note__alias__normalized_name',
|
||||
'date_start', 'date_end', 'fee', 'roles', ]
|
||||
ordering_fields = ['id', 'date_start', 'date_end', ]
|
||||
search_fields = ['$club__name', '$club__email', '$club__note__alias__name', '$club__note__alias__normalized_name',
|
||||
'$user__username', '$user__last_name', '$user__first_name', '$user__email',
|
||||
'$user__note__alias__name', '$user__note__alias__normalized_name', '$roles__name', ]
|
||||
|
@ -8,6 +8,7 @@ from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import transaction
|
||||
from django.forms import CheckboxSelectMultiple
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -57,6 +58,7 @@ class ProfileForm(forms.ModelForm):
|
||||
self.fields['address'].widget.attrs.update({"placeholder": "4 avenue des Sciences, 91190 GIF-SUR-YVETTE"})
|
||||
self.fields['promotion'].widget.attrs.update({"max": timezone.now().year})
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, commit=True):
|
||||
if not self.instance.section or (("department" in self.changed_data
|
||||
or "promotion" in self.changed_data) and "section" not in self.changed_data):
|
||||
@ -148,6 +150,7 @@ class ClubForm(forms.ModelForm):
|
||||
"membership_fee_unpaid": AmountInput(),
|
||||
"parent_club": Autocomplete(
|
||||
Club,
|
||||
resetable=True,
|
||||
attrs={
|
||||
'api_url': '/api/members/club/',
|
||||
}
|
||||
@ -161,7 +164,7 @@ class MembershipForm(forms.ModelForm):
|
||||
soge = forms.BooleanField(
|
||||
label=_("Inscription paid by Société Générale"),
|
||||
required=False,
|
||||
help_text=_("Check this case is the Société Générale paid the inscription."),
|
||||
help_text=_("Check this case if the Société Générale paid the inscription."),
|
||||
)
|
||||
|
||||
credit_type = forms.ModelChoiceField(
|
||||
|
@ -7,6 +7,7 @@ def create_bde_and_kfet(apps, schema_editor):
|
||||
"""
|
||||
Club = apps.get_model("member", "club")
|
||||
NoteClub = apps.get_model("note", "noteclub")
|
||||
Alias = apps.get_model("note", "alias")
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
polymorphic_ctype_id = ContentType.objects.get_for_model(NoteClub).id
|
||||
|
||||
@ -45,6 +46,19 @@ def create_bde_and_kfet(apps, schema_editor):
|
||||
polymorphic_ctype_id=polymorphic_ctype_id,
|
||||
)
|
||||
|
||||
Alias.objects.get_or_create(
|
||||
id=5,
|
||||
note_id=5,
|
||||
name="BDE",
|
||||
normalized_name="bde",
|
||||
)
|
||||
Alias.objects.get_or_create(
|
||||
id=6,
|
||||
note_id=6,
|
||||
name="Kfet",
|
||||
normalized_name="kfet",
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
|
@ -0,0 +1,50 @@
|
||||
import sys
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def give_note_account_permissions(apps, schema_editor):
|
||||
"""
|
||||
Automatically manage the membership of the Note account.
|
||||
"""
|
||||
User = apps.get_model("auth", "user")
|
||||
Membership = apps.get_model("member", "membership")
|
||||
Role = apps.get_model("permission", "role")
|
||||
|
||||
note = User.objects.filter(username="note")
|
||||
if not note.exists():
|
||||
# We are in a test environment, don't log error message
|
||||
if len(sys.argv) > 1 and sys.argv[1] == 'test':
|
||||
return
|
||||
print("Warning: Note account was not found. The note account was not imported.")
|
||||
print("Make sure you have imported the NK15 database. The new import script handles correctly the permissions.")
|
||||
print("This migration will be ignored, you can re-run it if you forgot the note account or ignore it if you "
|
||||
"don't want this account.")
|
||||
return
|
||||
|
||||
note = note.get()
|
||||
|
||||
# Set for the two clubs a large expiration date and the correct role.
|
||||
for m in Membership.objects.filter(user_id=note.id).all():
|
||||
m.date_end = "3142-12-12"
|
||||
m.roles.set(Role.objects.filter(name="PC Kfet").all())
|
||||
m.save()
|
||||
# By default, the note account is only authorized to be logged from localhost.
|
||||
note.password = "ipbased$127.0.0.1"
|
||||
note.is_active = True
|
||||
note.save()
|
||||
# Ensure that the note of the account is disabled
|
||||
note.note.inactivity_reason = 'forced'
|
||||
note.note.is_active = False
|
||||
note.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('member', '0005_remove_null_tag_on_charfields'),
|
||||
('permission', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(give_note_account_permissions),
|
||||
]
|
@ -7,7 +7,7 @@ import os
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
from django.template import loader
|
||||
from django.urls import reverse, reverse_lazy
|
||||
@ -271,6 +271,7 @@ class Club(models.Model):
|
||||
self._force_save = True
|
||||
self.save(force_update=True)
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if not self.require_memberships:
|
||||
@ -312,6 +313,7 @@ class Membership(models.Model):
|
||||
|
||||
roles = models.ManyToManyField(
|
||||
"permission.Role",
|
||||
related_name="memberships",
|
||||
verbose_name=_("roles"),
|
||||
)
|
||||
|
||||
@ -406,6 +408,7 @@ class Membership(models.Model):
|
||||
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
|
||||
parent_membership.save()
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
||||
@ -475,8 +478,13 @@ class Membership(models.Model):
|
||||
# to treasurers.
|
||||
transaction.valid = False
|
||||
from treasury.models import SogeCredit
|
||||
soge_credit = SogeCredit.objects.get_or_create(user=self.user)[0]
|
||||
soge_credit.refresh_from_db()
|
||||
if SogeCredit.objects.filter(user=self.user).exists():
|
||||
soge_credit = SogeCredit.objects.get(user=self.user)
|
||||
else:
|
||||
soge_credit = SogeCredit(user=self.user)
|
||||
soge_credit._force_save = True
|
||||
soge_credit.save(force_insert=True)
|
||||
soge_credit.refresh_from_db()
|
||||
transaction.save(force_insert=True)
|
||||
transaction.refresh_from_db()
|
||||
soge_credit.transactions.add(transaction)
|
||||
|
@ -14,7 +14,7 @@ function create_alias (e) {
|
||||
}).done(function () {
|
||||
// Reload table
|
||||
$('#alias_table').load(location.pathname + ' #alias_table')
|
||||
addMsg('Alias ajouté', 'success')
|
||||
addMsg(gettext('Alias successfully added'), 'success')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
@ -22,7 +22,7 @@ function create_alias (e) {
|
||||
|
||||
/**
|
||||
* On click of "delete", delete the alias
|
||||
* @param Integer button_id Alias id to remove
|
||||
* @param button_id:Integer Alias id to remove
|
||||
*/
|
||||
function delete_button (button_id) {
|
||||
$.ajax({
|
||||
@ -30,7 +30,7 @@ function delete_button (button_id) {
|
||||
method: 'DELETE',
|
||||
headers: { 'X-CSRFTOKEN': CSRF_TOKEN }
|
||||
}).done(function () {
|
||||
addMsg('Alias supprimé', 'success')
|
||||
addMsg(gettext('Alias successfully deleted'), 'success')
|
||||
$('#alias_table').load(location.pathname + ' #alias_table')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
|
@ -43,8 +43,24 @@ class UserTable(tables.Table):
|
||||
|
||||
section = tables.Column(accessor='profile__section')
|
||||
|
||||
# Override the column to let replace the URL
|
||||
email = tables.EmailColumn(linkify=lambda record: "mailto:{}".format(record.email))
|
||||
|
||||
balance = tables.Column(accessor='note__balance', verbose_name=_("Balance"))
|
||||
|
||||
def render_email(self, record, value):
|
||||
# Replace the email by a dash if the user can't see the profile detail
|
||||
# Replace also the URL
|
||||
if not PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile):
|
||||
value = "—"
|
||||
record.email = value
|
||||
return value
|
||||
|
||||
def render_section(self, record, value):
|
||||
return value \
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile) \
|
||||
else "—"
|
||||
|
||||
def render_balance(self, record, value):
|
||||
return pretty_money(value)\
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", record.note) else "—"
|
||||
@ -112,7 +128,7 @@ class MembershipTable(tables.Table):
|
||||
fee=0,
|
||||
)
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(),
|
||||
"member:add_membership", empty_membership): # If the user has right
|
||||
"member.add_membership", empty_membership): # If the user has right
|
||||
renew_url = reverse_lazy('member:club_renew_membership',
|
||||
kwargs={"pk": record.pk})
|
||||
t = format_html(
|
||||
|
@ -13,15 +13,29 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% if additional_fee_renewal %}
|
||||
<div class="alert alert-warning">
|
||||
{% if renewal %}
|
||||
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||
The user is not a member of the club·s {{ clubs }}. An additional fee of {{ pretty_fee }}
|
||||
will be charged to renew automatically the membership in this/these club·s.
|
||||
{% endblocktrans %}
|
||||
{% if club.name == "Kfet" %} {# Auto-renewal #}
|
||||
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||
The user is not a member of the club·s {{ clubs }}. An additional fee of {{ pretty_fee }}
|
||||
will be charged to renew automatically the membership in this/these club·s.
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||
The user is not a member of the club·s {{ clubs }}. Please create the required memberships,
|
||||
otherwise it will fail.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||
This club has parents {{ clubs }}. An additional fee of {{ pretty_fee }}
|
||||
will be charged to adhere automatically to this/these club·s.
|
||||
{% endblocktrans %}
|
||||
{% if club.name == "Kfet" %}
|
||||
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||
This club has parents {{ clubs }}. An additional fee of {{ pretty_fee }}
|
||||
will be charged to adhere automatically to this/these club·s.
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||
This club has parents {{ clubs }}. Please make sure that the user is a member of this or these club·s,
|
||||
otherwise the creation of this membership will fail.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -48,7 +48,7 @@
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans 'Manage aliases' %} ({{ club.note.alias_set.all|length }})
|
||||
{% trans 'Manage aliases' %} ({{ club.note.alias.all|length }})
|
||||
</a>
|
||||
</dd>
|
||||
|
||||
|
@ -21,33 +21,35 @@
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans 'Manage aliases' %} ({{ user_object.note.alias_set.all|length }})
|
||||
{% trans 'Manage aliases' %} ({{ user_object.note.alias.all|length }})
|
||||
</a>
|
||||
</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.profile.section }}</dd>
|
||||
{% if "member.view_profile"|has_perm:user_object.profile %}
|
||||
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.profile.section }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'email'|capfirst %}</dt>
|
||||
<dd class="col-xl-6"><a href="mailto:{{ user_object.email }}">{{ user_object.email }}</a></dd>
|
||||
<dt class="col-xl-6">{% trans 'email'|capfirst %}</dt>
|
||||
<dd class="col-xl-6"><a href="mailto:{{ user_object.email }}">{{ user_object.email }}</a></dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'phone number'|capfirst %}</dt>
|
||||
<dd class="col-xl-6"><a href="tel:{{ user_object.profile.phone_number }}">{{ user_object.profile.phone_number }}</a>
|
||||
</dd>
|
||||
<dt class="col-xl-6">{% trans 'phone number'|capfirst %}</dt>
|
||||
<dd class="col-xl-6"><a href="tel:{{ user_object.profile.phone_number }}">{{ user_object.profile.phone_number }}</a>
|
||||
</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.profile.address }}</dd>
|
||||
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.profile.address }}</dd>
|
||||
|
||||
{% if "note.view_note"|has_perm:user_object.note %}
|
||||
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
|
||||
{% if user_object.note and "note.view_note"|has_perm:user_object.note %}
|
||||
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd>
|
||||
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
{% if user_object.pk == user_object.pk %}
|
||||
{% if user_object.pk == user.pk %}
|
||||
<div class="text-center">
|
||||
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
|
||||
<i class="fa fa-cogs"></i>{% trans 'API token' %}
|
||||
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% load i18n perms %}
|
||||
|
||||
{% block content %}
|
||||
{% if "member.change_profile_registration_valid"|has_perm:user %}
|
||||
{% if can_manage_registrations %}
|
||||
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
|
||||
<i class="fa fa-user-plus"></i> {% trans "Registrations" %}
|
||||
</a>
|
||||
|
0
apps/member/templatetags/__init__.py
Normal file
22
apps/member/templatetags/memberinfo.py
Normal file
@ -0,0 +1,22 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import date
|
||||
|
||||
from django import template
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from ..models import Club, Membership
|
||||
|
||||
|
||||
def is_member(user, club):
|
||||
if isinstance(user, str):
|
||||
club = User.objects.get(username=user)
|
||||
if isinstance(club, str):
|
||||
club = Club.objects.get(name=club)
|
||||
return Membership.objects\
|
||||
.filter(user=user, club=club, date_start__lte=date.today(), date_end__gte=date.today()).exists()
|
||||
|
||||
|
||||
register = template.Library()
|
||||
register.filter("is_member", is_member)
|
@ -41,7 +41,7 @@ class TemplateLoggedInTests(TestCase):
|
||||
password="adminadmin",
|
||||
permission_mask=3,
|
||||
))
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 200)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
|
||||
|
||||
def test_logout(self):
|
||||
response = self.client.get(reverse("logout"))
|
||||
|
@ -5,17 +5,20 @@ import hashlib
|
||||
import os
|
||||
from datetime import date, timedelta
|
||||
|
||||
from api.tests import TestAPI
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.db.models import Q
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from member.models import Club, Membership, Profile
|
||||
from note.models import Alias, NoteSpecial
|
||||
from permission.models import Role
|
||||
from treasury.models import SogeCredit
|
||||
|
||||
from ..api.views import ClubViewSet, MembershipViewSet, ProfileViewSet
|
||||
from ..models import Club, Membership, Profile
|
||||
|
||||
"""
|
||||
Create some users and clubs and test that all pages are rendering properly
|
||||
and that memberships are working.
|
||||
@ -205,7 +208,7 @@ class TestMemberships(TestCase):
|
||||
first_name="Toto",
|
||||
bank="Le matelas",
|
||||
))
|
||||
self.assertRedirects(response, club.get_absolute_url(), 302, 200)
|
||||
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
|
||||
|
||||
self.assertTrue(Membership.objects.filter(user=user, club=club).exists())
|
||||
|
||||
@ -244,9 +247,9 @@ class TestMemberships(TestCase):
|
||||
first_name="Toto",
|
||||
bank="Bank",
|
||||
))
|
||||
self.assertRedirects(response, club.get_absolute_url(), 302, 200)
|
||||
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
|
||||
|
||||
response = self.client.get(user.profile.get_absolute_url())
|
||||
response = self.client.get(club.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_auto_join_kfet_when_join_bde_with_soge(self):
|
||||
@ -273,7 +276,7 @@ class TestMemberships(TestCase):
|
||||
first_name="Toto",
|
||||
bank="Société générale",
|
||||
))
|
||||
self.assertRedirects(response, bde.get_absolute_url(), 302, 200)
|
||||
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
|
||||
|
||||
self.assertTrue(Membership.objects.filter(user=user, club=bde).exists())
|
||||
self.assertTrue(Membership.objects.filter(user=user, club=kfet).exists())
|
||||
@ -403,3 +406,46 @@ class TestMemberships(TestCase):
|
||||
self.user.password = "custom_nk15$1$" + salt + "|" + hashed
|
||||
self.user.save()
|
||||
self.assertTrue(self.user.check_password(password))
|
||||
|
||||
|
||||
class TestMemberAPI(TestAPI):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
self.user.profile.registration_valid = True
|
||||
self.user.profile.email_confirmed = True
|
||||
self.user.profile.phone_number = "0600000000"
|
||||
self.user.profile.section = "1A0"
|
||||
self.user.profile.department = "A0"
|
||||
self.user.profile.address = "Earth"
|
||||
self.user.profile.save()
|
||||
|
||||
self.club = Club.objects.create(
|
||||
name="totoclub",
|
||||
parent_club=Club.objects.get(name="BDE"),
|
||||
membership_start=date(year=1970, month=1, day=1),
|
||||
membership_end=date(year=2040, month=1, day=1),
|
||||
membership_duration=365 * 10,
|
||||
)
|
||||
self.bde_membership = Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE"))
|
||||
self.membership = Membership.objects.create(user=self.user, club=self.club)
|
||||
self.membership.roles.add(Role.objects.get(name="Bureau de club"))
|
||||
self.membership.save()
|
||||
|
||||
def test_club_api(self):
|
||||
"""
|
||||
Load Club API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(ClubViewSet, "/api/members/club/")
|
||||
|
||||
def test_profile_api(self):
|
||||
"""
|
||||
Load Profile API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(ProfileViewSet, "/api/members/profile/")
|
||||
|
||||
def test_membership_api(self):
|
||||
"""
|
||||
Load Membership API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(MembershipViewSet, "/api/members/membership/")
|
||||
|
@ -38,6 +38,7 @@ class CustomLoginView(LoginView):
|
||||
"""
|
||||
form_class = CustomAuthenticationForm
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
logout(self.request)
|
||||
_set_current_user_and_ip(form.get_user(), self.request.session, None)
|
||||
@ -69,13 +70,15 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
form.fields['email'].required = True
|
||||
form.fields['email'].help_text = _("This address must be valid.")
|
||||
|
||||
context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
|
||||
data=self.request.POST if self.request.POST else None)
|
||||
if not self.object.profile.report_frequency:
|
||||
del context['profile_form'].fields["last_report"]
|
||||
if PermissionBackend.check_perm(self.request.user, "member.change_profile", context['user_object'].profile):
|
||||
context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
|
||||
data=self.request.POST if self.request.POST else None)
|
||||
if not self.object.profile.report_frequency:
|
||||
del context['profile_form'].fields["last_report"]
|
||||
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
Check if ProfileForm is correct
|
||||
@ -155,8 +158,12 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1))
|
||||
context['history_list'] = history_table
|
||||
|
||||
club_list = Membership.objects.filter(user=user, date_end__gte=date.today())\
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))
|
||||
club_list = Membership.objects.filter(user=user, date_end__gte=date.today() - timedelta(days=15))\
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))\
|
||||
.order_by("club__name", "-date_start")
|
||||
# Display only the most recent membership
|
||||
club_list = club_list.distinct("club__name")\
|
||||
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else club_list
|
||||
membership_table = MembershipTable(data=club_list, prefix='membership-')
|
||||
membership_table.paginate(per_page=10, page=self.request.GET.get("membership-page", 1))
|
||||
context['club_list'] = membership_table
|
||||
@ -164,6 +171,8 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
# Check permissions to see if the authenticated user can lock/unlock the note
|
||||
with transaction.atomic():
|
||||
modified_note = NoteUser.objects.get(pk=user.note.pk)
|
||||
# Don't log these tests
|
||||
modified_note._no_signal = True
|
||||
modified_note.is_active = True
|
||||
modified_note.inactivity_reason = 'manual'
|
||||
context["can_lock_note"] = user.note.is_active and PermissionBackend\
|
||||
@ -176,6 +185,7 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
context["can_force_lock"] = user.note.is_active and PermissionBackend\
|
||||
.check_perm(self.request.user, "note.change_note_is_active", modified_note)
|
||||
old_note._force_save = True
|
||||
old_note._no_signal = True
|
||||
old_note.save()
|
||||
modified_note.refresh_from_db()
|
||||
modified_note.is_active = True
|
||||
@ -225,6 +235,13 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
pre_registered_users = User.objects.filter(PermissionBackend.filter_queryset(self.request.user, User, "view"))\
|
||||
.filter(profile__registration_valid=False)
|
||||
context["can_manage_registrations"] = pre_registered_users.exists()
|
||||
return context
|
||||
|
||||
|
||||
class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
@ -238,8 +255,8 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
note = context['object'].note
|
||||
context["aliases"] = AliasTable(note.alias_set.filter(PermissionBackend
|
||||
.filter_queryset(self.request.user, Alias, "view")).all())
|
||||
context["aliases"] = AliasTable(
|
||||
note.alias.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
|
||||
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
|
||||
note=context["object"].note,
|
||||
name="",
|
||||
@ -269,6 +286,7 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
|
||||
self.object = self.get_object()
|
||||
return self.form_valid(form) if form.is_valid() else self.form_invalid(form)
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
"""Save image to note"""
|
||||
image = form.cleaned_data['image']
|
||||
@ -389,7 +407,8 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
if PermissionBackend.check_perm(self.request.user, "member.change_club_membership_start", club):
|
||||
club.update_membership_dates()
|
||||
# managers list
|
||||
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club")\
|
||||
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
|
||||
date_start__lte=date.today(), date_end__gte=date.today())\
|
||||
.order_by('user__last_name').all()
|
||||
context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
|
||||
# transaction history
|
||||
@ -402,8 +421,12 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
# member list
|
||||
club_member = Membership.objects.filter(
|
||||
club=club,
|
||||
date_end__gte=date.today(),
|
||||
).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))
|
||||
date_end__gte=date.today() - timedelta(days=15),
|
||||
).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))\
|
||||
.order_by("user__username", "-date_start")
|
||||
# Display only the most recent membership
|
||||
club_member = club_member.distinct("user__username")\
|
||||
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else club_member
|
||||
|
||||
membership_table = MembershipTable(data=club_member, prefix="membership-")
|
||||
membership_table.paginate(per_page=5, page=self.request.GET.get('membership-page', 1))
|
||||
@ -435,8 +458,8 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
note = context['object'].note
|
||||
context["aliases"] = AliasTable(note.alias_set.filter(PermissionBackend
|
||||
.filter_queryset(self.request.user, Alias, "view")).all())
|
||||
context["aliases"] = AliasTable(note.alias.filter(
|
||||
PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
|
||||
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
|
||||
note=context["object"].note,
|
||||
name="",
|
||||
@ -607,6 +630,9 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
bank = form.cleaned_data["bank"]
|
||||
soge = form.cleaned_data["soge"] and not user.profile.soge and (club.name == "BDE" or club.name == "Kfet")
|
||||
|
||||
if not credit_type:
|
||||
credit_amount = 0
|
||||
|
||||
if not soge and user.note.balance + credit_amount < fee and not Membership.objects.filter(
|
||||
club__name="Kfet",
|
||||
user=user,
|
||||
@ -628,6 +654,16 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
form.add_error('user', _('User is already a member of the club'))
|
||||
error = True
|
||||
|
||||
# Must join the parent club before joining this club, except for the Kfet club where it can be at the same time.
|
||||
if club.name != "Kfet" and club.parent_club and not Membership.objects.filter(
|
||||
user=form.instance.user,
|
||||
club=club.parent_club,
|
||||
date_start__lte=timezone.now(),
|
||||
date_end__gte=club.parent_club.membership_end,
|
||||
).exists():
|
||||
form.add_error('user', _('User is not a member of the parent club') + ' ' + club.parent_club.name)
|
||||
error = True
|
||||
|
||||
if club.membership_start and form.instance.date_start < club.membership_start:
|
||||
form.add_error('user', _("The membership must start after {:%m-%d-%Y}.")
|
||||
.format(form.instance.club.membership_start))
|
||||
@ -642,14 +678,17 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"):
|
||||
if not last_name:
|
||||
form.add_error('last_name', _("This field is required."))
|
||||
error = True
|
||||
if not first_name:
|
||||
form.add_error('first_name', _("This field is required."))
|
||||
error = True
|
||||
if not bank and credit_type.special_type == "Chèque":
|
||||
form.add_error('bank', _("This field is required."))
|
||||
return self.form_invalid(form)
|
||||
error = True
|
||||
|
||||
return not error
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
Create membership, check that all is good, make transactions
|
||||
@ -659,6 +698,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \
|
||||
.get(pk=self.kwargs["club_pk"])
|
||||
user = form.instance.user
|
||||
old_membership = None
|
||||
else: # get from url for renewal
|
||||
old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
|
||||
club = old_membership.club
|
||||
@ -733,6 +773,9 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
member_role = Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all() \
|
||||
if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all() \
|
||||
if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all()
|
||||
# Set the same roles as before
|
||||
if old_membership:
|
||||
member_role = member_role.union(old_membership.roles.all())
|
||||
form.instance.roles.set(member_role)
|
||||
form.instance._force_save = True
|
||||
form.instance.save()
|
||||
@ -770,7 +813,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
return ret
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('member:club_detail', kwargs={'pk': self.object.club.id})
|
||||
return reverse_lazy('member:user_detail', kwargs={'pk': self.object.user.id})
|
||||
|
||||
|
||||
class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
|
@ -1,5 +1,6 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.core.exceptions import ValidationError
|
||||
@ -14,29 +15,37 @@ from permission.backends import PermissionBackend
|
||||
|
||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
||||
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
|
||||
from ..models.notes import Note, Alias
|
||||
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial
|
||||
from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
|
||||
|
||||
|
||||
class NotePolymorphicViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
The djangorestframework plugin will get all `Note` objects (with polymorhism), serialize it to JSON with the given serializer,
|
||||
The djangorestframework plugin will get all `Note` objects (with polymorhism),
|
||||
serialize it to JSON with the given serializer,
|
||||
then render it on /api/note/note/
|
||||
"""
|
||||
queryset = Note.objects.all()
|
||||
queryset = Note.objects.order_by('id')
|
||||
serializer_class = NotePolymorphicSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ['polymorphic_ctype', 'is_active', ]
|
||||
search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model', ]
|
||||
ordering_fields = ['alias__name', 'alias__normalized_name']
|
||||
filterset_fields = ['alias__name', 'polymorphic_ctype', 'is_active', 'balance', 'last_negative', 'created_at', ]
|
||||
search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model',
|
||||
'$noteuser__user__last_name', '$noteuser__user__first_name', '$noteuser__user__email',
|
||||
'$noteuser__user__email', '$noteclub__club__email', ]
|
||||
ordering_fields = ['alias__name', 'alias__normalized_name', 'balance', 'created_at', ]
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Parse query and apply filters.
|
||||
:return: The filtered set of requested notes
|
||||
"""
|
||||
queryset = super().get_queryset().distinct()
|
||||
user = self.request.user
|
||||
get_current_session().setdefault("permission_mask", 42)
|
||||
queryset = self.queryset.filter(PermissionBackend.filter_queryset(user, Note, "view")
|
||||
| PermissionBackend.filter_queryset(user, NoteUser, "view")
|
||||
| PermissionBackend.filter_queryset(user, NoteClub, "view")
|
||||
| PermissionBackend.filter_queryset(user, NoteSpecial, "view")).distinct()
|
||||
|
||||
alias = self.request.query_params.get("alias", ".*")
|
||||
queryset = queryset.filter(
|
||||
@ -54,11 +63,12 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/aliases/
|
||||
"""
|
||||
queryset = Alias.objects.all()
|
||||
queryset = Alias.objects
|
||||
serializer_class = AliasSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||
ordering_fields = ['name', 'normalized_name']
|
||||
filterset_fields = ['note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||
ordering_fields = ['name', 'normalized_name', ]
|
||||
|
||||
def get_serializer_class(self):
|
||||
serializer_class = self.serializer_class
|
||||
@ -104,11 +114,12 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
||||
|
||||
|
||||
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
||||
queryset = Alias.objects.all()
|
||||
queryset = Alias.objects
|
||||
serializer_class = ConsumerSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
|
||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||
ordering_fields = ['name', 'normalized_name']
|
||||
filterset_fields = ['note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||
ordering_fields = ['name', 'normalized_name', ]
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
@ -116,29 +127,31 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
||||
:return: The filtered set of requested aliases
|
||||
"""
|
||||
|
||||
queryset = super().get_queryset()
|
||||
queryset = super().get_queryset().distinct()
|
||||
# Sqlite doesn't support ORDER BY in subqueries
|
||||
queryset = queryset.order_by("name") \
|
||||
if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset
|
||||
|
||||
alias = self.request.query_params.get("alias", ".*")
|
||||
alias = self.request.query_params.get("alias", None)
|
||||
queryset = queryset.prefetch_related('note')
|
||||
# We match first an alias if it is matched without normalization,
|
||||
# then if the normalized pattern matches a normalized alias.
|
||||
queryset = queryset.filter(
|
||||
name__iregex="^" + alias
|
||||
).union(
|
||||
queryset.filter(
|
||||
Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
||||
& ~Q(name__iregex="^" + alias)
|
||||
),
|
||||
all=True).union(
|
||||
queryset.filter(
|
||||
Q(normalized_name__iregex="^" + alias.lower())
|
||||
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
||||
& ~Q(name__iregex="^" + alias)
|
||||
),
|
||||
all=True)
|
||||
|
||||
if alias:
|
||||
# We match first an alias if it is matched without normalization,
|
||||
# then if the normalized pattern matches a normalized alias.
|
||||
queryset = queryset.filter(
|
||||
name__iregex="^" + alias
|
||||
).union(
|
||||
queryset.filter(
|
||||
Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
||||
& ~Q(name__iregex="^" + alias)
|
||||
),
|
||||
all=True).union(
|
||||
queryset.filter(
|
||||
Q(normalized_name__iregex="^" + alias.lower())
|
||||
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
||||
& ~Q(name__iregex="^" + alias)
|
||||
),
|
||||
all=True)
|
||||
|
||||
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
|
||||
else queryset.order_by("name")
|
||||
@ -152,10 +165,11 @@ class TemplateCategoryViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `TemplateCategory` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/note/transaction/category/
|
||||
"""
|
||||
queryset = TemplateCategory.objects.order_by("name").all()
|
||||
queryset = TemplateCategory.objects.order_by('name')
|
||||
serializer_class = TemplateCategorySerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$name', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'templates', 'templates__name']
|
||||
search_fields = ['$name', '$templates__name', ]
|
||||
|
||||
|
||||
class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
||||
@ -164,11 +178,12 @@ class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
||||
The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/note/transaction/template/
|
||||
"""
|
||||
queryset = TransactionTemplate.objects.order_by("name").all()
|
||||
queryset = TransactionTemplate.objects.order_by('name')
|
||||
serializer_class = TransactionTemplateSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
filterset_fields = ['name', 'amount', 'display', 'category', ]
|
||||
search_fields = ['$name', ]
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
filterset_fields = ['name', 'amount', 'display', 'category', 'category__name', ]
|
||||
search_fields = ['$name', '$category__name', ]
|
||||
ordering_fields = ['amount', ]
|
||||
|
||||
|
||||
class TransactionViewSet(ReadProtectedModelViewSet):
|
||||
@ -177,10 +192,17 @@ class TransactionViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Transaction` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/note/transaction/transaction/
|
||||
"""
|
||||
queryset = Transaction.objects.order_by("-created_at").all()
|
||||
queryset = Transaction.objects.order_by('-created_at')
|
||||
serializer_class = TransactionPolymorphicSerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$reason', ]
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
filterset_fields = ['source', 'source_alias', 'source__alias__name', 'source__alias__normalized_name',
|
||||
'destination', 'destination_alias', 'destination__alias__name',
|
||||
'destination__alias__normalized_name', 'quantity', 'polymorphic_ctype', 'amount',
|
||||
'created_at', 'valid', 'invalidity_reason', ]
|
||||
search_fields = ['$reason', '$source_alias', '$source__alias__name', '$source__alias__normalized_name',
|
||||
'$destination_alias', '$destination__alias__name', '$destination__alias__normalized_name',
|
||||
'$invalidity_reason', ]
|
||||
ordering_fields = ['created_at', 'amount', ]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
from django.db.models.signals import post_save, pre_delete
|
||||
from django.db.models.signals import pre_delete, pre_save, post_save
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from . import signals
|
||||
@ -17,6 +17,15 @@ class NoteConfig(AppConfig):
|
||||
"""
|
||||
Define app internal signals to interact with other apps
|
||||
"""
|
||||
pre_save.connect(
|
||||
signals.pre_save_note,
|
||||
sender="note.noteuser",
|
||||
)
|
||||
pre_save.connect(
|
||||
signals.pre_save_note,
|
||||
sender="note.noteclub",
|
||||
)
|
||||
|
||||
post_save.connect(
|
||||
signals.save_user_note,
|
||||
sender=settings.AUTH_USER_MODEL,
|
||||
|
@ -8,7 +8,7 @@ from django.conf.global_settings import DEFAULT_FROM_EMAIL
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.mail import send_mail
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.db import models, transaction
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -93,6 +93,7 @@ class Note(PolymorphicModel):
|
||||
delta = timezone.now() - self.last_negative
|
||||
return "{:d} jours".format(delta.days)
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Save note with it's alias (called in polymorphic children)
|
||||
@ -108,12 +109,16 @@ class Note(PolymorphicModel):
|
||||
|
||||
# Save alias
|
||||
a.note = self
|
||||
# Consider that if the name of the note could be changed, then the alias can be created.
|
||||
# It does not mean that any alias can be created.
|
||||
a._force_save = True
|
||||
a.save(force_insert=True)
|
||||
else:
|
||||
# Check if the name of the note changed without changing the normalized form of the alias
|
||||
alias = Alias.objects.get(normalized_name=Alias.normalize(str(self)))
|
||||
if alias.name != str(self):
|
||||
alias.name = str(self)
|
||||
alias._force_save = True
|
||||
alias.save()
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
@ -154,19 +159,6 @@ class NoteUser(Note):
|
||||
def pretty(self):
|
||||
return _("%(user)s's note") % {'user': str(self.user)}
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.pk and self.balance < 0:
|
||||
old_note = NoteUser.objects.get(pk=self.pk)
|
||||
super().save(*args, **kwargs)
|
||||
if old_note.balance >= 0:
|
||||
# Passage en négatif
|
||||
self.last_negative = timezone.now()
|
||||
self._force_save = True
|
||||
self.save(*args, **kwargs)
|
||||
self.send_mail_negative_balance()
|
||||
else:
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def send_mail_negative_balance(self):
|
||||
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
||||
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
||||
@ -195,19 +187,6 @@ class NoteClub(Note):
|
||||
def pretty(self):
|
||||
return _("Note of %(club)s club") % {'club': str(self.club)}
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.pk and self.balance < 0:
|
||||
old_note = NoteClub.objects.get(pk=self.pk)
|
||||
super().save(*args, **kwargs)
|
||||
if old_note.balance >= 0:
|
||||
# Passage en négatif
|
||||
self.last_negative = timezone.now()
|
||||
self._force_save = True
|
||||
self.save(*args, **kwargs)
|
||||
self.send_mail_negative_balance()
|
||||
else:
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def send_mail_negative_balance(self):
|
||||
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
||||
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
||||
@ -269,6 +248,7 @@ class Alias(models.Model):
|
||||
note = models.ForeignKey(
|
||||
Note,
|
||||
on_delete=models.PROTECT,
|
||||
related_name="alias",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -310,6 +290,7 @@ class Alias(models.Model):
|
||||
pass
|
||||
self.normalized_name = normalized_name
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
@ -170,19 +170,21 @@ class Transaction(PolymorphicModel):
|
||||
previous_source_balance = self.source.balance
|
||||
previous_dest_balance = self.destination.balance
|
||||
|
||||
source_balance = self.source.balance
|
||||
dest_balance = self.destination.balance
|
||||
source_balance = previous_source_balance
|
||||
dest_balance = previous_dest_balance
|
||||
|
||||
created = self.pk is None
|
||||
to_transfer = self.amount * self.quantity
|
||||
if not created and not self.valid and not hasattr(self, "_force_save"):
|
||||
to_transfer = self.total
|
||||
if not created:
|
||||
# Revert old transaction
|
||||
old_transaction = Transaction.objects.get(pk=self.pk)
|
||||
# We make a select for update to avoid concurrency issues
|
||||
old_transaction = Transaction.objects.select_for_update().get(pk=self.pk)
|
||||
# Check that nothing important changed
|
||||
for field_name in ["source_id", "destination_id", "quantity", "amount"]:
|
||||
if getattr(self, field_name) != getattr(old_transaction, field_name):
|
||||
raise ValidationError(_("You can't update the {field} on a Transaction. "
|
||||
"Please invalidate it and create one other.").format(field=field_name))
|
||||
if not hasattr(self, "_force_save"):
|
||||
for field_name in ["source_id", "destination_id", "quantity", "amount"]:
|
||||
if getattr(self, field_name) != getattr(old_transaction, field_name):
|
||||
raise ValidationError(_("You can't update the {field} on a Transaction. "
|
||||
"Please invalidate it and create one other.").format(field=field_name))
|
||||
|
||||
if old_transaction.valid == self.valid:
|
||||
# Don't change anything
|
||||
@ -215,14 +217,14 @@ class Transaction(PolymorphicModel):
|
||||
# When source == destination, no money is transferred and no transaction is created
|
||||
return
|
||||
|
||||
# We refresh the notes with the "select for update" tag to avoid concurrency issues
|
||||
self.source = Note.objects.filter(pk=self.source_id).select_for_update().get()
|
||||
self.destination = Note.objects.filter(pk=self.destination_id).select_for_update().get()
|
||||
self.source = Note.objects.select_for_update().get(pk=self.source_id)
|
||||
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
|
||||
|
||||
# Check that the amounts stay between big integer bounds
|
||||
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 "
|
||||
"or the destination note is not active."))
|
||||
|
||||
@ -237,9 +239,11 @@ class Transaction(PolymorphicModel):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# Save notes
|
||||
self.source.refresh_from_db()
|
||||
self.source.balance += diff_source
|
||||
self.source._force_save = True
|
||||
self.source.save()
|
||||
self.destination.refresh_from_db()
|
||||
self.destination.balance += diff_dest
|
||||
self.destination._force_save = True
|
||||
self.destination.save()
|
||||
@ -268,11 +272,12 @@ class RecurrentTransaction(Transaction):
|
||||
)
|
||||
|
||||
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(
|
||||
_("The destination of this transaction must equal to the destination of the template."))
|
||||
return super().clean()
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
return super().save(*args, **kwargs)
|
||||
@ -323,6 +328,7 @@ class SpecialTransaction(Transaction):
|
||||
raise(ValidationError(_("A special transaction is only possible between a"
|
||||
" Note associated to a payment method and a User or a Club")))
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
@ -1,6 +1,8 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def save_user_note(instance, raw, **_kwargs):
|
||||
"""
|
||||
@ -25,10 +27,21 @@ def save_club_note(instance, raw, **_kwargs):
|
||||
instance.note.save()
|
||||
|
||||
|
||||
def pre_save_note(instance, raw, **_kwargs):
|
||||
if not raw and instance.pk and not hasattr(instance, "_no_signal") and instance.balance < 0:
|
||||
from note.models import Note
|
||||
old_note = Note.objects.get(pk=instance.pk)
|
||||
if old_note.balance >= 0:
|
||||
# Passage en négatif
|
||||
instance.last_negative = timezone.now()
|
||||
instance.send_mail_negative_balance()
|
||||
|
||||
|
||||
def delete_transaction(instance, **_kwargs):
|
||||
"""
|
||||
Whenever we want to delete a transaction (caution with this), we ensure the transaction is invalid first.
|
||||
"""
|
||||
if not hasattr(instance, "_no_signal"):
|
||||
instance.valid = False
|
||||
instance._force_save = True
|
||||
instance.save()
|
||||
|
@ -29,7 +29,6 @@ $(document).ready(function () {
|
||||
// Switching in double consumptions mode should update the layout
|
||||
$('#double_conso').change(function () {
|
||||
$('#consos_list_div').removeClass('d-none')
|
||||
$('#user_select_div').attr('class', 'col-xl-4')
|
||||
$('#infos_div').attr('class', 'col-sm-5 col-xl-6')
|
||||
|
||||
const note_list_obj = $('#note_list')
|
||||
@ -48,7 +47,6 @@ $(document).ready(function () {
|
||||
|
||||
$('#single_conso').change(function () {
|
||||
$('#consos_list_div').addClass('d-none')
|
||||
$('#user_select_div').attr('class', 'col-xl-7')
|
||||
$('#infos_div').attr('class', 'col-sm-5 col-md-4')
|
||||
|
||||
const consos_list_obj = $('#consos_list')
|
||||
@ -224,17 +222,15 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
|
||||
if (!isNaN(source.balance)) {
|
||||
const newBalance = source.balance - quantity * amount
|
||||
if (newBalance <= -5000) {
|
||||
addMsg('Attention, La transaction depuis la note ' + source_alias + ' a été réalisée avec ' +
|
||||
'succès, mais la note émettrice ' + source_alias + ' est en négatif sévère.',
|
||||
'danger', 30000)
|
||||
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)
|
||||
} else if (newBalance < 0) {
|
||||
addMsg('Attention, La transaction depuis la note ' + source_alias + ' a été réalisée avec ' +
|
||||
'succès, mais la note émettrice ' + source_alias + ' est en négatif.',
|
||||
'warning', 30000)
|
||||
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
|
||||
'but the emitter note %s is negative.'), [source_alias, source_alias]), 'warning', 30000)
|
||||
}
|
||||
if (source.membership && source.membership.date_end < new Date().toISOString()) {
|
||||
addMsg('Attention : la note émettrice ' + source.name + " n'est plus adhérente.",
|
||||
'danger', 30000)
|
||||
addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.'), [source_alias]),
|
||||
'danger', 30000)
|
||||
}
|
||||
}
|
||||
reset()
|
||||
@ -255,7 +251,7 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
|
||||
template: template
|
||||
}).done(function () {
|
||||
reset()
|
||||
addMsg("La transaction n'a pas pu être validée pour cause de solde insuffisant.", 'danger', 10000)
|
||||
addMsg(gettext("The transaction couldn't be validated because of insufficient balance."), 'danger', 10000)
|
||||
}).fail(function () {
|
||||
reset()
|
||||
errMsg(e.responseJSON)
|
@ -67,7 +67,11 @@ $(document).ready(function () {
|
||||
|
||||
last.quantity = 1
|
||||
|
||||
if (!last.note.user) {
|
||||
if (last.note.club) {
|
||||
$('#last_name').val(last.note.name)
|
||||
$('#first_name').val(last.note.name)
|
||||
}
|
||||
else if (!last.note.user) {
|
||||
$.getJSON('/api/note/note/' + last.note.id + '/?format=json', function (note) {
|
||||
last.note.user = note.user
|
||||
$.getJSON('/api/user/' + last.note.user + '/', function (user) {
|
||||
@ -235,20 +239,20 @@ $('#btn_transfer').click(function () {
|
||||
|
||||
if (!amount_field.val() || isNaN(amount_field.val()) || amount_field.val() <= 0) {
|
||||
amount_field.addClass('is-invalid')
|
||||
$('#amount-required').html('<strong>Ce champ est requis et doit comporter un nombre décimal strictement positif.</strong>')
|
||||
$('#amount-required').html('<strong>' + gettext('This field is required and must contain a decimal positive number.') + '</strong>')
|
||||
error = true
|
||||
}
|
||||
|
||||
const amount = Math.floor(100 * amount_field.val())
|
||||
if (amount > 2147483647) {
|
||||
amount_field.addClass('is-invalid')
|
||||
$('#amount-required').html('<strong>Le montant ne doit pas excéder 21474836.47 €.</strong>')
|
||||
$('#amount-required').html('<strong>' + gettext('The amount must stay under 21,474,836.47 €.') + '</strong>')
|
||||
error = true
|
||||
}
|
||||
|
||||
if (!reason_field.val()) {
|
||||
if (!reason_field.val() && $('#type_transfer').is(':checked')) {
|
||||
reason_field.addClass('is-invalid')
|
||||
$('#reason-required').html('<strong>Ce champ est requis.</strong>')
|
||||
$('#reason-required').html('<strong>' + gettext('This field is required.') + '</strong>')
|
||||
error = true
|
||||
}
|
||||
|
||||
@ -274,9 +278,8 @@ $('#btn_transfer').click(function () {
|
||||
[...sources_notes_display].forEach(function (source) {
|
||||
[...dests_notes_display].forEach(function (dest) {
|
||||
if (source.note.id === dest.note.id) {
|
||||
addMsg('Attention : la transaction de ' + pretty_money(amount) + ' de la note ' + source.name +
|
||||
' vers la note ' + dest.name + " n'a pas été faite car il s'agit de la même note au départ" +
|
||||
" et à l'arrivée.", 'warning', 10000)
|
||||
addMsg(interpolate(gettext('Warning: the transaction of %s from %s to %s was not made because ' +
|
||||
'it is the same source and destination note.'), [pretty_money(amount), source.name, dest.name]), 'warning', 10000)
|
||||
LOCK = false
|
||||
return
|
||||
}
|
||||
@ -296,43 +299,35 @@ $('#btn_transfer').click(function () {
|
||||
destination_alias: dest.name
|
||||
}).done(function () {
|
||||
if (source.note.membership && source.note.membership.date_end < new Date().toISOString()) {
|
||||
addMsg('Attention : la note émettrice ' + source.name + " n'est plus adhérente.",
|
||||
'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()) {
|
||||
addMsg('Attention : la note destination ' + dest.name + " n'est plus adhérente.",
|
||||
'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)) {
|
||||
const newBalance = source.note.balance - source.quantity * dest.quantity * amount
|
||||
if (newBalance <= -5000) {
|
||||
addMsg('Le transfert de ' +
|
||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' +
|
||||
source.name + ' vers la note ' + dest.name + ' a été fait avec succès, ' +
|
||||
'mais la note émettrice est en négatif sévère.', 'danger', 10000)
|
||||
addMsg(interpolate(gettext('Warning, the transaction of %s from the note %s to the note %s succeed, but the emitter note %s is very negative.'),
|
||||
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, source.name]), 'danger', 10000)
|
||||
reset()
|
||||
return
|
||||
} else if (newBalance < 0) {
|
||||
addMsg('Le transfert de ' +
|
||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' +
|
||||
source.name + ' vers la note ' + dest.name + ' a été fait avec succès, ' +
|
||||
'mais la note émettrice est en négatif.', 'warning', 10000)
|
||||
addMsg(interpolate(gettext('Warning, the transaction of %s from the note %s to the note %s succeed, but the emitter note %s is negative.'),
|
||||
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, source.name]), 'danger', 10000)
|
||||
reset()
|
||||
return
|
||||
}
|
||||
}
|
||||
addMsg('Le transfert de ' +
|
||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
||||
' vers la note ' + dest.name + ' a été fait avec succès !', 'success', 10000)
|
||||
addMsg(interpolate(gettext('Transfer of %s from %s to %s succeed!'),
|
||||
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name]), 'success', 10000)
|
||||
|
||||
reset()
|
||||
}).fail(function (err) { // do it again but valid = false
|
||||
const errObj = JSON.parse(err.responseText)
|
||||
if (errObj.non_field_errors) {
|
||||
addMsg('Le transfert de ' +
|
||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
||||
' vers la note ' + dest.name + ' a échoué : ' + errObj.non_field_errors, 'danger')
|
||||
addMsg(interpolate(gettext('Transfer of %s from %s to %s failed: %s'),
|
||||
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, errObj.non_field_errors]), 'danger')
|
||||
LOCK = false
|
||||
return
|
||||
}
|
||||
@ -352,17 +347,15 @@ $('#btn_transfer').click(function () {
|
||||
destination: dest.note.id,
|
||||
destination_alias: dest.name
|
||||
}).done(function () {
|
||||
addMsg('Le transfert de ' +
|
||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
||||
' vers la note ' + dest.name + ' a échoué : Solde insuffisant', 'danger', 10000)
|
||||
addMsg(interpolate(gettext('Transfer of %s from %s to %s failed: %s'),
|
||||
[pretty_money(source.quantity * dest.quantity * amount), source.name, + dest.name, gettext('insufficient funds')]), 'danger', 10000)
|
||||
reset()
|
||||
}).fail(function (err) {
|
||||
const errObj = JSON.parse(err.responseText)
|
||||
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
||||
if (!error) { error = err.responseText }
|
||||
addMsg('Le transfert de ' +
|
||||
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
||||
' vers la note ' + dest.name + ' a échoué : ' + error, 'danger')
|
||||
addMsg(interpolate(gettext('Transfer of %s from %s to %s failed: %s'),
|
||||
[pretty_money(source.quantity * dest.quantity * amount), source.name, + dest.name, error]), 'danger')
|
||||
LOCK = false
|
||||
})
|
||||
})
|
||||
@ -388,7 +381,7 @@ $('#btn_transfer').click(function () {
|
||||
alias = sources_notes_display[0].name
|
||||
source_id = user_note.id
|
||||
dest_id = special_note
|
||||
reason = 'Retrait ' + $('#credit_type option:selected').text().toLowerCase()
|
||||
reason = 'Retrait ' + $('#debit_type option:selected').text().toLowerCase()
|
||||
if (given_reason.length > 0) { reason += ' (' + given_reason + ')' }
|
||||
}
|
||||
$.post('/api/note/transaction/transaction/',
|
||||
@ -408,14 +401,14 @@ $('#btn_transfer').click(function () {
|
||||
first_name: $('#first_name').val(),
|
||||
bank: $('#bank').val()
|
||||
}).done(function () {
|
||||
addMsg('Le crédit/retrait a bien été effectué !', 'success', 10000)
|
||||
if (user_note.membership && user_note.membership.date_end < new Date().toISOString()) { addMsg('Attention : la note ' + alias + " n'est plus adhérente.", 'danger', 10000) }
|
||||
addMsg(gettext('Credit/debit succeed!'), 'success', 10000)
|
||||
if (user_note.membership && user_note.membership.date_end < new Date().toISOString()) { addMsg(gettext('Warning, the emitter note %s is no more a BDE member.'), 'danger', 10000) }
|
||||
reset()
|
||||
}).fail(function (err) {
|
||||
const errObj = JSON.parse(err.responseText)
|
||||
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
||||
if (!error) { error = err.responseText }
|
||||
addMsg('Le crédit/retrait a échoué : ' + error, 'danger', 10000)
|
||||
addMsg(interpolate(gettext('Credit/debit failed: %s'), [error]), 'danger', 10000)
|
||||
LOCK = false
|
||||
})
|
||||
}
|
@ -10,22 +10,22 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-sm-5 col-md-4" id="infos_div">
|
||||
<div class="row">
|
||||
<div class="row justify-content-center justify-content-md-end">
|
||||
{# User details column #}
|
||||
<div class="col">
|
||||
<div class="card bg-light border-success mb-4 text-center">
|
||||
<div class="col picture-col">
|
||||
<div class="card bg-light mb-4 text-center">
|
||||
<a id="profile_pic_link" href="#">
|
||||
<img src="{% static "member/img/default_picture.png" %}"
|
||||
id="profile_pic" alt="" class="card-img-top">
|
||||
id="profile_pic" alt="" class="card-img-top d-none d-sm-block">
|
||||
</a>
|
||||
<div class="card-body text-center text-break">
|
||||
<span id="user_note"></span>
|
||||
<div class="card-body text-center text-break p-2">
|
||||
<span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# User selection column #}
|
||||
<div class="col-xl-7" id="user_select_div">
|
||||
<div class="col-xl" id="user_select_div">
|
||||
<div class="card bg-light border-success mb-4">
|
||||
<div class="card-header">
|
||||
<p class="card-text font-weight-bold">
|
||||
@ -44,6 +44,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Summary of consumption and consume button #}
|
||||
<div class="col-xl-5 d-none" id="consos_list_div">
|
||||
<div class="card bg-light border-info mb-4">
|
||||
@ -65,7 +66,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{# Show last used buttons #}
|
||||
<div class="card bg-light mb-4">
|
||||
<div class="card-header">
|
||||
@ -159,7 +159,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script type="text/javascript" src="{% static "js/consos.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "note/js/consos.js" %}"></script>
|
||||
<script type="text/javascript">
|
||||
{% for button in highlighted %}
|
||||
{% if button.display %}
|
||||
|
@ -34,21 +34,21 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="row justify-content-center">
|
||||
{# Preview note profile (picture, username and balance) #}
|
||||
<div class="col-md-3" id="note_infos_div">
|
||||
<div class="card bg-light border-success shadow mb-4 pt-4 text-center">
|
||||
<div class="col-md picture-col" id="note_infos_div">
|
||||
<div class="card bg-light mb-4 text-center">
|
||||
<a id="profile_pic_link" href="#"><img src="{% static "member/img/default_picture.png" %}"
|
||||
id="profile_pic" alt="" class="img-fluid rounded mx-auto"></a>
|
||||
<div class="card-body text-center">
|
||||
<span id="user_note"></span>
|
||||
<div class="card-body text-center p-2">
|
||||
<span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# list of emitters #}
|
||||
<div class="col-md-3" id="emitters_div">
|
||||
<div class="card bg-light border-success shadow mb-4">
|
||||
<div class="card bg-light mb-4">
|
||||
<div class="card-header">
|
||||
<p class="card-text font-weight-bold">
|
||||
<label for="source_note" id="source_note_label">{% trans "Select emitters" %}</label>
|
||||
@ -75,7 +75,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
{# list of receiver #}
|
||||
<div class="col-md-3" id="dests_div">
|
||||
<div class="card bg-light border-info shadow mb-4">
|
||||
<div class="card bg-light mb-4">
|
||||
<div class="card-header">
|
||||
<p class="card-text font-weight-bold" id="dest_title">
|
||||
<label for="dest_note" id="dest_note_label">{% trans "Select receivers" %}</label>
|
||||
@ -97,8 +97,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</div>
|
||||
|
||||
{# Information on transaction (amount, reason, name,...) #}
|
||||
<div class="col-md-3" id="external_div">
|
||||
<div class="card bg-light border-warning shadow mb-4">
|
||||
<div class="col-md" id="external_div">
|
||||
<div class="card bg-light mb-4">
|
||||
<div class="card-header">
|
||||
<p class="card-text font-weight-bold">
|
||||
{% trans "Action" %}
|
||||
@ -153,7 +153,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</div>
|
||||
</div>
|
||||
{# transaction history #}
|
||||
<div class="card shadow mb-4" id="history">
|
||||
<div class="card mb-4" id="history">
|
||||
<div class="card-header">
|
||||
<p class="card-text font-weight-bold">
|
||||
{% trans "Recent transactions history" %}
|
||||
@ -176,5 +176,5 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
select_receveirs_label = "{% trans "Select receivers" %}";
|
||||
transfer_type_label = "{% trans "Transfer type" %}";
|
||||
</script>
|
||||
<script src="/static/js/transfer.js"></script>
|
||||
<script src="{% static "note/js/transfer.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
@ -1,15 +1,20 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from api.tests import TestAPI
|
||||
from member.models import Club, Membership
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from member.models import Club, Membership
|
||||
from note.models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
|
||||
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias
|
||||
from django.utils import timezone
|
||||
from permission.models import Role
|
||||
|
||||
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\
|
||||
TransactionTemplateViewSet, TransactionViewSet
|
||||
from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
|
||||
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note
|
||||
|
||||
|
||||
class TestTransactions(TestCase):
|
||||
fixtures = ('initial', )
|
||||
@ -297,8 +302,8 @@ class TestTransactions(TestCase):
|
||||
|
||||
def test_render_search_transactions(self):
|
||||
response = self.client.get(reverse("note:transactions", args=(self.user.note.pk,)), data=dict(
|
||||
source=self.second_user.note.alias_set.first().id,
|
||||
destination=self.user.note.alias_set.first().id,
|
||||
source=self.second_user.note.alias.first().id,
|
||||
destination=self.user.note.alias.first().id,
|
||||
type=[ContentType.objects.get_for_model(Transaction).id],
|
||||
reason="test",
|
||||
valid=True,
|
||||
@ -363,3 +368,69 @@ class TestTransactions(TestCase):
|
||||
self.assertTrue(Alias.objects.filter(name="test_updated_alias").exists())
|
||||
response = self.client.delete("/api/note/alias/" + str(alias.pk) + "/")
|
||||
self.assertEqual(response.status_code, 204)
|
||||
|
||||
|
||||
class TestNoteAPI(TestAPI):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user)
|
||||
membership.roles.add(Role.objects.get(name="Respo info"))
|
||||
membership.save()
|
||||
Membership.objects.create(club=Club.objects.get(name="Kfet"), user=self.user)
|
||||
self.user.note.last_negative = timezone.now()
|
||||
self.user.note.save()
|
||||
|
||||
self.transaction = Transaction.objects.create(
|
||||
source=Note.objects.first(),
|
||||
destination=self.user.note,
|
||||
amount=4200,
|
||||
reason="Test transaction",
|
||||
)
|
||||
self.user.note.refresh_from_db()
|
||||
Alias.objects.create(note=self.user.note, name="I am a ¢omplex alias")
|
||||
|
||||
self.category = TemplateCategory.objects.create(name="Test")
|
||||
self.template = TransactionTemplate.objects.create(
|
||||
name="Test",
|
||||
destination=Club.objects.get(name="BDE").note,
|
||||
category=self.category,
|
||||
amount=100,
|
||||
description="Test template",
|
||||
)
|
||||
|
||||
def test_alias_api(self):
|
||||
"""
|
||||
Load Alias API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(AliasViewSet, "/api/note/alias/")
|
||||
|
||||
def test_consumer_api(self):
|
||||
"""
|
||||
Load Consumer API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(ConsumerViewSet, "/api/note/consumer/")
|
||||
|
||||
def test_note_api(self):
|
||||
"""
|
||||
Load Note API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(NotePolymorphicViewSet, "/api/note/note/")
|
||||
|
||||
def test_template_category_api(self):
|
||||
"""
|
||||
Load TemplateCategory API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(TemplateCategoryViewSet, "/api/note/transaction/category/")
|
||||
|
||||
def test_transaction_template_api(self):
|
||||
"""
|
||||
Load TemplateTemplate API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(TransactionTemplateViewSet, "/api/note/transaction/template/")
|
||||
|
||||
def test_transaction_api(self):
|
||||
"""
|
||||
Load Transaction API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(TransactionViewSet, "/api/note/transaction/transaction/")
|
||||
|
@ -144,7 +144,7 @@ class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, Up
|
||||
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
"""
|
||||
The Magic View that make people pay their beer and burgers.
|
||||
(Most of the magic happens in the dark world of Javascript see `note_kfet/static/js/consos.js`)
|
||||
(Most of the magic happens in the dark world of Javascript see `static/note/js/consos.js`)
|
||||
"""
|
||||
model = Transaction
|
||||
template_name = "note/conso_form.html"
|
||||
|
@ -1,8 +1,9 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from api.viewsets import ReadOnlyProtectedModelViewSet
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter
|
||||
|
||||
from .serializers import PermissionSerializer, RoleSerializer
|
||||
from ..models import Permission, Role
|
||||
@ -14,10 +15,11 @@ class PermissionViewSet(ReadOnlyProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Permission` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/permission/permission/
|
||||
"""
|
||||
queryset = Permission.objects.all()
|
||||
queryset = Permission.objects.order_by('id')
|
||||
serializer_class = PermissionSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['model', 'type', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['model', 'type', 'query', 'mask', 'field', 'permanent', ]
|
||||
search_fields = ['$model__name', '$query', '$description', ]
|
||||
|
||||
|
||||
class RoleViewSet(ReadOnlyProtectedModelViewSet):
|
||||
@ -26,7 +28,8 @@ class RoleViewSet(ReadOnlyProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `RolePermission` objects, serialize it to JSON with the given serializer
|
||||
then render it on /api/permission/roles/
|
||||
"""
|
||||
queryset = Role.objects.all()
|
||||
queryset = Role.objects.order_by('id')
|
||||
serializer_class = RoleSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['role', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'permissions', 'for_club', 'memberships__user', ]
|
||||
search_fields = ['$name', '$for_club__name', ]
|
||||
|
@ -1,10 +1,9 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from time import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sessions.models import Session
|
||||
from note_kfet.middlewares import get_current_session
|
||||
|
||||
@ -33,12 +32,16 @@ def memoize(f):
|
||||
sess_funs = new_sess_funs
|
||||
|
||||
def func(*args, **kwargs):
|
||||
if settings.DEBUG:
|
||||
# Don't memoize in DEBUG mode
|
||||
return f(*args, **kwargs)
|
||||
# if settings.DEBUG:
|
||||
# # Don't memoize in DEBUG mode
|
||||
# return f(*args, **kwargs)
|
||||
|
||||
nonlocal last_collect
|
||||
|
||||
if "test" in sys.argv:
|
||||
# In a test environment, don't memoize permissions
|
||||
return f(*args, **kwargs)
|
||||
|
||||
if time() - last_collect > 60:
|
||||
# Clear cache
|
||||
collect()
|
||||
|
@ -115,7 +115,7 @@
|
||||
"type": "view",
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": true,
|
||||
"permanent": false,
|
||||
"description": "Voir les aliases des notes des clubs et des adhérents du club Kfet"
|
||||
}
|
||||
},
|
||||
@ -799,12 +799,12 @@
|
||||
"member",
|
||||
"membership"
|
||||
],
|
||||
"query": "{\"club\": [\"club\"]}",
|
||||
"query": "{}",
|
||||
"type": "change",
|
||||
"mask": 3,
|
||||
"field": "roles",
|
||||
"permanent": false,
|
||||
"description": "Modifier les rôles d'un adhérent d'un club"
|
||||
"description": "Modifier les rôles d'une adhésion"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -819,7 +819,7 @@
|
||||
"type": "change",
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"permanent": true,
|
||||
"description": "Modifier son profil"
|
||||
}
|
||||
},
|
||||
@ -1103,7 +1103,7 @@
|
||||
"treasury",
|
||||
"sogecredit"
|
||||
],
|
||||
"query": "{\"credit_transaction\": null}",
|
||||
"query": "{}",
|
||||
"type": "add",
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
@ -2081,7 +2081,7 @@
|
||||
],
|
||||
"query": "{}",
|
||||
"type": "change",
|
||||
"mask": 1,
|
||||
"mask": 2,
|
||||
"field": "invalidity_reason",
|
||||
"permanent": false,
|
||||
"description": "Modifier la raison d'invalidité d'une transaction"
|
||||
@ -2647,6 +2647,230 @@
|
||||
"description": "Changer l'image de la note de son club"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 170,
|
||||
"fields": {
|
||||
"model": [
|
||||
"note",
|
||||
"alias"
|
||||
],
|
||||
"query": "{\"note__is_active\": true}",
|
||||
"type": "add",
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Ajouter n'importe quel alias à une note non bloquée"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 171,
|
||||
"fields": {
|
||||
"model": [
|
||||
"note",
|
||||
"alias"
|
||||
],
|
||||
"query": "{\"note__is_active\": true}",
|
||||
"type": "delete",
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Supprimer n'importe quel alias à une note non bloquée"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 172,
|
||||
"fields": {
|
||||
"model": [
|
||||
"treasury",
|
||||
"remittance"
|
||||
],
|
||||
"query": "{}",
|
||||
"type": "view",
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir toutes les remises"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 173,
|
||||
"fields": {
|
||||
"model": [
|
||||
"treasury",
|
||||
"remittance"
|
||||
],
|
||||
"query": "{}",
|
||||
"type": "add",
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Ajouter une remise"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 174,
|
||||
"fields": {
|
||||
"model": [
|
||||
"treasury",
|
||||
"remittance"
|
||||
],
|
||||
"query": "{}",
|
||||
"type": "change",
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Modifier une remise"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 175,
|
||||
"fields": {
|
||||
"model": [
|
||||
"treasury",
|
||||
"remittance"
|
||||
],
|
||||
"query": "{}",
|
||||
"type": "delete",
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Supprimer une remise"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 176,
|
||||
"fields": {
|
||||
"model": [
|
||||
"auth",
|
||||
"user"
|
||||
],
|
||||
"query": "{\"profile__registration_valid\": false}",
|
||||
"type": "change",
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Modifier n'importe quel utilisateur non encore inscrit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 177,
|
||||
"fields": {
|
||||
"model": [
|
||||
"member",
|
||||
"profile"
|
||||
],
|
||||
"query": "{\"registration_valid\": false}",
|
||||
"type": "change",
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Modifier n'importe quel profil non encore inscrit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 178,
|
||||
"fields": {
|
||||
"model": [
|
||||
"note",
|
||||
"alias"
|
||||
],
|
||||
"query": "{}",
|
||||
"type": "view",
|
||||
"mask": 3,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir tous les alias, y compris ceux des non adhérents"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 179,
|
||||
"fields": {
|
||||
"model": [
|
||||
"note",
|
||||
"alias"
|
||||
],
|
||||
"query": "{\"note__noteuser__user\": [\"user\"]}",
|
||||
"type": "view",
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": true,
|
||||
"description": "Voir ses propres alias, pour toujours"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 180,
|
||||
"fields": {
|
||||
"model": [
|
||||
"auth",
|
||||
"user"
|
||||
],
|
||||
"query": "{\"profile__registration_valid\": false}",
|
||||
"type": "view",
|
||||
"mask": 2,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir n'importe quel utilisateur non encore inscrit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 181,
|
||||
"fields": {
|
||||
"model": [
|
||||
"member",
|
||||
"profile"
|
||||
],
|
||||
"query": "{\"registration_valid\": false}",
|
||||
"type": "view",
|
||||
"mask": 2,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir n'importe quel profil non encore inscrit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 182,
|
||||
"fields": {
|
||||
"model": [
|
||||
"auth",
|
||||
"user"
|
||||
],
|
||||
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
|
||||
"type": "view",
|
||||
"mask": 2,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Voir n'importe quel utilisateur qui est adhérent BDE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 183,
|
||||
"fields": {
|
||||
"model": [
|
||||
"note",
|
||||
"note"
|
||||
],
|
||||
"query": "{}",
|
||||
"type": "change",
|
||||
"mask": 1,
|
||||
"field": "display_image",
|
||||
"permanent": false,
|
||||
"description": "Changer l'image de n'importe quelle note"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.role",
|
||||
"pk": 1,
|
||||
@ -2717,7 +2941,8 @@
|
||||
157,
|
||||
158,
|
||||
159,
|
||||
160
|
||||
160,
|
||||
179
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -2778,14 +3003,14 @@
|
||||
62,
|
||||
127,
|
||||
133,
|
||||
135,
|
||||
136,
|
||||
141,
|
||||
142,
|
||||
150,
|
||||
166,
|
||||
167,
|
||||
168
|
||||
168,
|
||||
182
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -2799,7 +3024,9 @@
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27
|
||||
27,
|
||||
30,
|
||||
33
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -2821,6 +3048,7 @@
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
51,
|
||||
53,
|
||||
54,
|
||||
55,
|
||||
@ -2844,13 +3072,24 @@
|
||||
137,
|
||||
138,
|
||||
139,
|
||||
140,
|
||||
143,
|
||||
146,
|
||||
147,
|
||||
150,
|
||||
151,
|
||||
163,
|
||||
164
|
||||
164,
|
||||
170,
|
||||
171,
|
||||
172,
|
||||
173,
|
||||
174,
|
||||
175,
|
||||
176,
|
||||
177,
|
||||
178,
|
||||
183
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -3024,7 +3263,21 @@
|
||||
166,
|
||||
167,
|
||||
168,
|
||||
169
|
||||
169,
|
||||
170,
|
||||
171,
|
||||
172,
|
||||
173,
|
||||
174,
|
||||
175,
|
||||
176,
|
||||
177,
|
||||
178,
|
||||
179,
|
||||
180,
|
||||
181,
|
||||
182,
|
||||
183
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -3050,10 +3303,20 @@
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
70,
|
||||
143,
|
||||
166,
|
||||
167,
|
||||
168
|
||||
168,
|
||||
170,
|
||||
171,
|
||||
176,
|
||||
177,
|
||||
178,
|
||||
179,
|
||||
180,
|
||||
181,
|
||||
182
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -3216,13 +3479,50 @@
|
||||
135,
|
||||
136,
|
||||
137,
|
||||
138,
|
||||
139,
|
||||
140,
|
||||
143,
|
||||
145,
|
||||
146,
|
||||
147,
|
||||
150
|
||||
150,
|
||||
176,
|
||||
177
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.role",
|
||||
"pk": 20,
|
||||
"fields": {
|
||||
"for_club": 2,
|
||||
"name": "PC Kfet",
|
||||
"permissions": [
|
||||
6,
|
||||
22,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
30,
|
||||
49,
|
||||
50,
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
58,
|
||||
137,
|
||||
143,
|
||||
147,
|
||||
150,
|
||||
166,
|
||||
167,
|
||||
168,
|
||||
176,
|
||||
177,
|
||||
180,
|
||||
181,
|
||||
182
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -43,7 +43,9 @@ class InstancedPermission:
|
||||
obj = copy(obj)
|
||||
obj.pk = 0
|
||||
with transaction.atomic():
|
||||
sid = transaction.savepoint()
|
||||
for o in self.model.model_class().objects.filter(pk=0).all():
|
||||
o._no_signal = True
|
||||
o._force_delete = True
|
||||
Model.delete(o)
|
||||
# An object with pk 0 wouldn't deleted. That's not normal, we alert admins.
|
||||
@ -61,9 +63,7 @@ class InstancedPermission:
|
||||
obj._no_signal = True
|
||||
Model.save(obj, force_insert=True)
|
||||
ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists()
|
||||
# Delete testing object
|
||||
obj._force_delete = True
|
||||
Model.delete(obj)
|
||||
transaction.savepoint_rollback(sid)
|
||||
|
||||
return ret
|
||||
|
||||
@ -199,6 +199,7 @@ class Permission(models.Model):
|
||||
if self.field and self.type not in {'view', 'change'}:
|
||||
raise ValidationError(_("Specifying field applies only to view and change permission types."))
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, **kwargs):
|
||||
self.full_clean()
|
||||
super().save()
|
||||
|
@ -14,6 +14,7 @@ class StrongDjangoObjectPermissions(DjangoObjectPermissions):
|
||||
This is a simple patch of this class that controls view access.
|
||||
"""
|
||||
|
||||
# The queryset is filtered, and permissions are more powerful than a simple check than just "can view this model"
|
||||
perms_map = {
|
||||
'GET': ['%(app_label)s.view_%(model_name)s'],
|
||||
'OPTIONS': [],
|
||||
|
@ -5,7 +5,6 @@ from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.template.defaultfilters import stringfilter
|
||||
from django import template
|
||||
from note.models import Transaction
|
||||
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
@ -25,21 +24,6 @@ def not_empty_model_list(model_name):
|
||||
return qs.exists()
|
||||
|
||||
|
||||
@stringfilter
|
||||
def not_empty_model_change_list(model_name):
|
||||
"""
|
||||
Return True if and only if the current user has right to change any object of the given model.
|
||||
"""
|
||||
user = get_current_authenticated_user()
|
||||
session = get_current_session()
|
||||
if user is None or isinstance(user, AnonymousUser):
|
||||
return False
|
||||
elif user.is_superuser and session.get("permission_mask", -1) >= 42:
|
||||
return True
|
||||
qs = model_list(model_name, "change")
|
||||
return qs.exists()
|
||||
|
||||
|
||||
@stringfilter
|
||||
def model_list(model_name, t="view", fetch=True):
|
||||
"""
|
||||
@ -68,33 +52,8 @@ def has_perm(perm, obj):
|
||||
return PermissionBackend.check_perm(get_current_authenticated_user(), perm, obj)
|
||||
|
||||
|
||||
def can_create_transaction():
|
||||
"""
|
||||
:return: True iff the authenticated user can create a transaction.
|
||||
"""
|
||||
user = get_current_authenticated_user()
|
||||
session = get_current_session()
|
||||
if user is None or isinstance(user, AnonymousUser):
|
||||
return False
|
||||
elif user.is_superuser and session.get("permission_mask", -1) >= 42:
|
||||
return True
|
||||
if session.get("can_create_transaction", None):
|
||||
return session.get("can_create_transaction", None) == 1
|
||||
|
||||
empty_transaction = Transaction(
|
||||
source=user.note,
|
||||
destination=user.note,
|
||||
quantity=1,
|
||||
amount=0,
|
||||
reason="Check permissions",
|
||||
)
|
||||
session["can_create_transaction"] = PermissionBackend.check_perm(user, "note.add_transaction", empty_transaction)
|
||||
return session.get("can_create_transaction") == 1
|
||||
|
||||
|
||||
register = template.Library()
|
||||
register.filter('not_empty_model_list', not_empty_model_list)
|
||||
register.filter('not_empty_model_change_list', not_empty_model_change_list)
|
||||
register.filter('model_list', model_list)
|
||||
register.filter('model_list_length', model_list_length)
|
||||
register.filter('has_perm', has_perm)
|
||||
|
@ -78,7 +78,7 @@ class PermissionQueryTestCase(TestCase):
|
||||
query = instanced.query
|
||||
model = perm.model.model_class()
|
||||
model.objects.filter(query).all()
|
||||
except (FieldError, AttributeError, ValueError, TypeError, JSONDecodeError):
|
||||
except (FieldError, AttributeError, ValueError, TypeError, JSONDecodeError): # pragma: no cover
|
||||
print("Query error for permission", perm)
|
||||
print("Query:", perm.query)
|
||||
if instanced.query:
|
||||
|
@ -6,6 +6,7 @@ from datetime import date
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.forms import HiddenInput
|
||||
from django.http import Http404
|
||||
@ -50,12 +51,15 @@ class ProtectQuerysetMixin:
|
||||
# No worry if the user change the hidden fields: a 403 error will be performed if the user tries to make
|
||||
# a custom request.
|
||||
# We could also delete the field, but some views might be affected.
|
||||
meta = form.instance._meta
|
||||
for key in form.base_fields:
|
||||
if not PermissionBackend.check_perm(self.request.user, "wei.change_weiregistration_" + key, self.object):
|
||||
if not PermissionBackend.check_perm(self.request.user,
|
||||
f"{meta.app_label}.change_{meta.model_name}_" + key, self.object):
|
||||
form.fields[key].widget = HiddenInput()
|
||||
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
Submit the form, if the page is a FormView.
|
||||
@ -81,7 +85,7 @@ class ProtectedCreateView(LoginRequiredMixin, CreateView):
|
||||
If not, a 403 error is displayed.
|
||||
"""
|
||||
|
||||
def get_sample_object(self):
|
||||
def get_sample_object(self): # pragma: no cover
|
||||
"""
|
||||
return a sample instance of the Model.
|
||||
It should be valid (can be stored properly in database), but must not collide with existing data.
|
||||
|
@ -44,6 +44,15 @@ class SignUpForm(UserCreationForm):
|
||||
fields = ('first_name', 'last_name', 'username', 'email', )
|
||||
|
||||
|
||||
class DeclareSogeAccountOpenedForm(forms.Form):
|
||||
soge_account = forms.BooleanField(
|
||||
label=_("I declare that I opened a bank account in the Société générale with the BDE partnership."),
|
||||
help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your "
|
||||
"account, you will have to pay the BDE membership."),
|
||||
required=False,
|
||||
)
|
||||
|
||||
|
||||
class WEISignupForm(forms.Form):
|
||||
wei_registration = forms.BooleanField(
|
||||
label=_("Register to the WEI"),
|
||||
@ -60,7 +69,7 @@ class ValidationForm(forms.Form):
|
||||
soge = forms.BooleanField(
|
||||
label=_("Inscription paid by Société Générale"),
|
||||
required=False,
|
||||
help_text=_("Check this case is the Société Générale paid the inscription."),
|
||||
help_text=_("Check this case if the Société Générale paid the inscription."),
|
||||
)
|
||||
|
||||
credit_type = forms.ModelChoiceField(
|
||||
|
@ -4,6 +4,8 @@
|
||||
import django_tables2 as tables
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from treasury.models import SogeCredit
|
||||
|
||||
|
||||
class FutureUserTable(tables.Table):
|
||||
"""
|
||||
@ -21,6 +23,7 @@ class FutureUserTable(tables.Table):
|
||||
fields = ('last_name', 'first_name', 'username', 'email', )
|
||||
model = User
|
||||
row_attrs = {
|
||||
'class': 'table-row',
|
||||
'class': lambda record: 'table-row'
|
||||
+ (' bg-warning' if SogeCredit.objects.filter(user=record).exists() else ''),
|
||||
'data-href': lambda record: record.pk
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3 mb-4">
|
||||
<div class="col-xl-5 mb-4">
|
||||
<div class="card bg-light shadow">
|
||||
<div class="card-header text-center" >
|
||||
<h4> {% trans "Account #" %} {{ object.pk }}</h4>
|
||||
@ -50,12 +50,19 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="col-md-7">
|
||||
<div class="card bg-light shadow">
|
||||
<form method="post">
|
||||
<div class="card-header text-center" >
|
||||
<h4> {% trans "Validate account" %}</h4>
|
||||
</div>
|
||||
|
||||
{% if declare_soge_account %}
|
||||
<div class="alert alert-info">
|
||||
{% trans "The user declared that he/she opened a bank account in the Société générale." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card-body" id="profile_infos">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
@ -104,7 +111,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
soge_field.change(fillFields);
|
||||
|
||||
{% if object.profile.soge %}
|
||||
{% if declare_soge_account %}
|
||||
soge_field.attr('checked', true);
|
||||
fillFields();
|
||||
{% endif %}
|
||||
|
@ -5,6 +5,7 @@ from django.conf import settings
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import resolve_url, redirect
|
||||
from django.urls import reverse_lazy
|
||||
@ -23,7 +24,7 @@ from permission.models import Role
|
||||
from permission.views import ProtectQuerysetMixin
|
||||
from treasury.models import SogeCredit
|
||||
|
||||
from .forms import SignUpForm, ValidationForm
|
||||
from .forms import SignUpForm, ValidationForm, DeclareSogeAccountOpenedForm
|
||||
from .tables import FutureUserTable
|
||||
from .tokens import email_validation_token
|
||||
|
||||
@ -41,12 +42,14 @@ class UserCreateView(CreateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["profile_form"] = self.second_form(self.request.POST if self.request.POST else None)
|
||||
context["soge_form"] = DeclareSogeAccountOpenedForm(self.request.POST if self.request.POST else None)
|
||||
del context["profile_form"].fields["section"]
|
||||
del context["profile_form"].fields["report_frequency"]
|
||||
del context["profile_form"].fields["last_report"]
|
||||
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
If the form is valid, then the user is created with is_active set to False
|
||||
@ -70,6 +73,13 @@ class UserCreateView(CreateView):
|
||||
|
||||
user.profile.send_email_validation_link()
|
||||
|
||||
soge_form = DeclareSogeAccountOpenedForm(self.request.POST)
|
||||
if "soge_account" in soge_form.data and soge_form.data["soge_account"]:
|
||||
# If the user declares that a bank account got opened, prepare the soge credit to warn treasurers
|
||||
soge_credit = SogeCredit(user=user)
|
||||
soge_credit._force_save = True
|
||||
soge_credit.save()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
@ -180,7 +190,7 @@ class FutureUserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableVi
|
||||
| Q(username__iregex="^" + pattern)
|
||||
)
|
||||
|
||||
return qs[:20]
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
@ -225,6 +235,8 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
fee += 8000
|
||||
ctx["total_fee"] = "{:.02f}".format(fee / 100, )
|
||||
|
||||
ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists()
|
||||
|
||||
return ctx
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
@ -234,6 +246,7 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
form.fields["first_name"].initial = user.first_name
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
user = self.get_object()
|
||||
|
||||
@ -304,6 +317,13 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
user.profile.save()
|
||||
user.refresh_from_db()
|
||||
|
||||
if not soge and SogeCredit.objects.filter(user=user).exists():
|
||||
# If the user declared that a bank account was opened but in the validation form the SoGé case was
|
||||
# unchecked, delete the associated credit
|
||||
soge_credit = SogeCredit.objects.get(user=user)
|
||||
soge_credit._force_delete = True
|
||||
soge_credit.delete()
|
||||
|
||||
if credit_type is not None and credit_amount > 0:
|
||||
# Credit the note
|
||||
SpecialTransaction.objects.create(
|
||||
@ -370,6 +390,8 @@ class FutureUserInvalidateView(ProtectQuerysetMixin, LoginRequiredMixin, View):
|
||||
user = User.objects.filter(profile__registration_valid=False)\
|
||||
.filter(PermissionBackend.filter_queryset(request.user, User, "change", "is_valid"))\
|
||||
.get(pk=self.kwargs["pk"])
|
||||
# Delete associated soge credits before
|
||||
SogeCredit.objects.filter(user=user).delete()
|
||||
|
||||
user.delete()
|
||||
|
||||
|
@ -16,10 +16,11 @@ class InvoiceViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Invoice` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/treasury/invoice/
|
||||
"""
|
||||
queryset = Invoice.objects.order_by("id").all()
|
||||
queryset = Invoice.objects.order_by('id')
|
||||
serializer_class = InvoiceSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['bde', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['bde', 'object', 'description', 'name', 'address', 'date', 'acquitted', 'locked', ]
|
||||
search_fields = ['$object', '$description', '$name', '$address', ]
|
||||
|
||||
|
||||
class ProductViewSet(ReadProtectedModelViewSet):
|
||||
@ -28,10 +29,11 @@ class ProductViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Product` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/treasury/product/
|
||||
"""
|
||||
queryset = Product.objects.order_by("invoice_id", "id").all()
|
||||
queryset = Product.objects.order_by('invoice_id', 'id')
|
||||
serializer_class = ProductSerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$designation', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['invoice', 'designation', 'quantity', 'amount', ]
|
||||
search_fields = ['$designation', '$invoice__object', ]
|
||||
|
||||
|
||||
class RemittanceTypeViewSet(ReadProtectedModelViewSet):
|
||||
@ -40,8 +42,11 @@ class RemittanceTypeViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `RemittanceType` objects, serialize it to JSON with the given serializer
|
||||
then render it on /api/treasury/remittance_type/
|
||||
"""
|
||||
queryset = RemittanceType.objects.order_by("id")
|
||||
queryset = RemittanceType.objects.order_by('id')
|
||||
serializer_class = RemittanceTypeSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['note', ]
|
||||
search_fields = ['$note__special_type', ]
|
||||
|
||||
|
||||
class RemittanceViewSet(ReadProtectedModelViewSet):
|
||||
@ -50,8 +55,11 @@ class RemittanceViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Remittance` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/treasury/remittance/
|
||||
"""
|
||||
queryset = Remittance.objects.order_by("id")
|
||||
queryset = Remittance.objects.order_by('id')
|
||||
serializer_class = RemittanceSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['date', 'remittance_type', 'comment', 'closed', 'transaction_proxies__transaction', ]
|
||||
search_fields = ['$remittance_type__note__special_type', '$comment', ]
|
||||
|
||||
|
||||
class SogeCreditViewSet(ReadProtectedModelViewSet):
|
||||
@ -60,5 +68,10 @@ class SogeCreditViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `SogeCredit` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/treasury/soge_credit/
|
||||
"""
|
||||
queryset = SogeCredit.objects.order_by("id")
|
||||
queryset = SogeCredit.objects.order_by('id')
|
||||
serializer_class = SogeCreditSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['user', 'user__last_name', 'user__first_name', 'user__email', 'user__note__alias__name',
|
||||
'user__note__alias__normalized_name', 'transactions', 'credit_transaction', ]
|
||||
search_fields = ['$user__last_name', '$user__first_name', '$user__email', '$user__note__alias__name',
|
||||
'$user__note__alias__normalized_name', ]
|
||||
|
@ -28,6 +28,8 @@ class TreasuryConfig(AppConfig):
|
||||
source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
|
||||
specialtransactionproxy=None,
|
||||
):
|
||||
SpecialTransactionProxy.objects.create(transaction=transaction, remittance=None)
|
||||
proxy = SpecialTransactionProxy(transaction=transaction, remittance=None)
|
||||
proxy._force_save = True
|
||||
proxy.save()
|
||||
|
||||
post_migrate.connect(setup_specialtransactions_proxies, sender=SpecialTransactionProxy)
|
||||
|
@ -4,6 +4,7 @@
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Submit
|
||||
from django import forms
|
||||
from django.db import transaction
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note_kfet.inputs import AmountInput
|
||||
|
||||
@ -149,6 +150,7 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
|
||||
self.instance.transaction.bank = cleaned_data["bank"]
|
||||
return cleaned_data
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, commit=True):
|
||||
"""
|
||||
Save the transaction and the remittance.
|
||||
|
@ -5,12 +5,12 @@ from datetime import date
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note.models import NoteSpecial, SpecialTransaction, MembershipTransaction
|
||||
from note.models import NoteSpecial, SpecialTransaction, MembershipTransaction, NoteUser
|
||||
|
||||
|
||||
class Invoice(models.Model):
|
||||
@ -76,6 +76,7 @@ class Invoice(models.Model):
|
||||
verbose_name=_("tex source"),
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
When an invoice is generated, we store the tex source.
|
||||
@ -228,6 +229,7 @@ class Remittance(models.Model):
|
||||
"""
|
||||
return sum(transaction.total for transaction in self.transactions.all())
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
# Check if all transactions have the right type.
|
||||
if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
|
||||
@ -255,6 +257,7 @@ class SpecialTransactionProxy(models.Model):
|
||||
Remittance,
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
related_name="transaction_proxies",
|
||||
verbose_name=_("Remittance"),
|
||||
)
|
||||
|
||||
@ -291,11 +294,12 @@ class SogeCredit(models.Model):
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
return self.credit_transaction.valid
|
||||
return self.credit_transaction and self.credit_transaction.valid
|
||||
|
||||
@property
|
||||
def amount(self):
|
||||
return sum(transaction.total for transaction in self.transactions.all()) + 8000
|
||||
return self.credit_transaction.total if self.valid \
|
||||
else sum(transaction.total for transaction in self.transactions.all()) + 8000
|
||||
|
||||
def invalidate(self):
|
||||
"""
|
||||
@ -305,10 +309,10 @@ class SogeCredit(models.Model):
|
||||
if self.valid:
|
||||
self.credit_transaction.valid = False
|
||||
self.credit_transaction.save()
|
||||
for transaction in self.transactions.all():
|
||||
transaction.valid = False
|
||||
transaction._force_save = True
|
||||
transaction.save()
|
||||
for tr in self.transactions.all():
|
||||
tr.valid = False
|
||||
tr._force_save = True
|
||||
tr.save()
|
||||
|
||||
def validate(self, force=False):
|
||||
if self.valid and not force:
|
||||
@ -320,18 +324,25 @@ class SogeCredit(models.Model):
|
||||
# Refresh credit amount
|
||||
self.save()
|
||||
self.credit_transaction.valid = True
|
||||
self.credit_transaction._force_save = True
|
||||
self.credit_transaction.save()
|
||||
self.save()
|
||||
|
||||
for transaction in self.transactions.all():
|
||||
transaction.valid = True
|
||||
transaction._force_save = True
|
||||
transaction.created_at = timezone.now()
|
||||
transaction.save()
|
||||
for tr in self.transactions.all():
|
||||
tr.valid = True
|
||||
tr._force_save = True
|
||||
tr.created_at = timezone.now()
|
||||
tr.save()
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
# This is a pre-registered user that declared that a SoGé account was opened.
|
||||
# No note exists yet.
|
||||
if not NoteUser.objects.filter(user=self.user).exists():
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
if not self.credit_transaction:
|
||||
self.credit_transaction = SpecialTransaction.objects.create(
|
||||
credit_transaction = SpecialTransaction(
|
||||
source=NoteSpecial.objects.get(special_type="Virement bancaire"),
|
||||
destination=self.user.note,
|
||||
quantity=1,
|
||||
@ -342,6 +353,10 @@ class SogeCredit(models.Model):
|
||||
bank="Société générale",
|
||||
valid=False,
|
||||
)
|
||||
credit_transaction._force_save = True
|
||||
credit_transaction.save()
|
||||
credit_transaction.refresh_from_db()
|
||||
self.credit_transaction = credit_transaction
|
||||
elif not self.valid:
|
||||
self.credit_transaction.amount = self.amount
|
||||
self.credit_transaction._force_save = True
|
||||
@ -361,14 +376,19 @@ class SogeCredit(models.Model):
|
||||
"Please ask her/him to credit the note before invalidating this credit."))
|
||||
|
||||
self.invalidate()
|
||||
for transaction in self.transactions.all():
|
||||
transaction._force_save = True
|
||||
transaction.valid = True
|
||||
transaction.created_at = timezone.now()
|
||||
transaction.save()
|
||||
self.credit_transaction.valid = False
|
||||
self.credit_transaction.reason += " (invalide)"
|
||||
self.credit_transaction.save()
|
||||
for tr in self.transactions.all():
|
||||
tr._force_save = True
|
||||
tr.valid = True
|
||||
tr.created_at = timezone.now()
|
||||
tr.save()
|
||||
if self.credit_transaction:
|
||||
# If the soge credit is deleted while the user is not validated yet,
|
||||
# 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)
|
||||
|
||||
class Meta:
|
||||
|
@ -10,9 +10,8 @@ def save_special_transaction(instance, created, **kwargs):
|
||||
"""
|
||||
|
||||
if not hasattr(instance, "_no_signal"):
|
||||
if instance.is_credit():
|
||||
if created and RemittanceType.objects.filter(note=instance.source).exists():
|
||||
SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save()
|
||||
else:
|
||||
if created and RemittanceType.objects.filter(note=instance.destination).exists():
|
||||
SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save()
|
||||
if created and RemittanceType.objects.filter(
|
||||
note=instance.source if instance.is_credit() else instance.destination).exists():
|
||||
proxy = SpecialTransactionProxy(transaction=instance, remittance=None)
|
||||
proxy._force_save = True
|
||||
proxy.save()
|
||||
|
@ -109,9 +109,6 @@ class SpecialTransactionTable(tables.Table):
|
||||
'a': {'class': 'btn btn-primary btn-danger'}
|
||||
}, )
|
||||
|
||||
def render_id(self, record):
|
||||
return record.specialtransactionproxy.pk
|
||||
|
||||
def render_amount(self, value):
|
||||
return pretty_money(value)
|
||||
|
||||
@ -147,4 +144,4 @@ class SogeCreditTable(tables.Table):
|
||||
|
||||
class Meta:
|
||||
model = SogeCredit
|
||||
fields = ('user', 'amount', 'valid', )
|
||||
fields = ('user', 'user__last_name', 'user__first_name', 'amount', 'valid', )
|
||||
|
@ -11,8 +11,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-xl-6 text-right">{% trans 'user'|capfirst %}</dt>
|
||||
<dd class="col-xl-6"><a href="{% url 'member:user_detail' pk=object.user.pk %}">{{ object.user }}</a></dd>
|
||||
<dt class="col-xl-6 text-right">{% trans 'last name'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ object.user.last_name }}</dd>
|
||||
|
||||
<dt class="col-xl-6 text-right">{% trans 'first name'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ object.user.first_name }}</dd>
|
||||
|
||||
<dt class="col-xl-6 text-right">{% trans 'username'|capfirst %}</dt>
|
||||
<dd class="col-xl-6"><a href="{% url 'member:user_detail' pk=object.user.pk %}">{{ object.user.username }}</a></dd>
|
||||
|
||||
{% if "note.view_note_balance"|has_perm:object.user.note %}
|
||||
<dt class="col-xl-6 text-right">{% trans 'balance'|capfirst %}</dt>
|
||||
|
@ -60,7 +60,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
let pattern = searchbar_obj.val();
|
||||
|
||||
$("#credits_table").load(location.pathname + "?search=" + pattern.replace(" ", "%20") + (
|
||||
invalid_only_obj.is(':checked') ? "&valid=false" : "") + " #credits_table");
|
||||
invalid_only_obj.is(':checked') ? "" : "&valid=1") + " #credits_table");
|
||||
|
||||
$(".table-row").click(function () {
|
||||
window.document.location = $(this).data("href");
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from api.tests import TestAPI
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
@ -8,7 +9,10 @@ from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from member.models import Membership, Club
|
||||
from note.models import SpecialTransaction, NoteSpecial, Transaction
|
||||
from treasury.models import Invoice, Product, Remittance, RemittanceType, SogeCredit
|
||||
|
||||
from ..api.views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet, \
|
||||
SogeCreditViewSet
|
||||
from ..models import Invoice, Product, Remittance, RemittanceType, SogeCredit
|
||||
|
||||
|
||||
class TestInvoices(TestCase):
|
||||
@ -366,11 +370,8 @@ class TestSogeCredits(TestCase):
|
||||
response = self.client.get(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
try:
|
||||
self.client.post(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)), data=dict(delete=True))
|
||||
raise AssertionError("It is not possible to delete the soge credit until the note is not credited.")
|
||||
except ValidationError:
|
||||
pass
|
||||
self.assertRaises(ValidationError, self.client.post,
|
||||
reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)), data=dict(delete=True))
|
||||
|
||||
SpecialTransaction.objects.create(
|
||||
source=NoteSpecial.objects.get(special_type="Carte bancaire"),
|
||||
@ -399,3 +400,82 @@ class TestSogeCredits(TestCase):
|
||||
"""
|
||||
response = self.client.get("/api/treasury/soge_credit/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestTreasuryAPI(TestAPI):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
self.invoice = Invoice.objects.create(
|
||||
id=1,
|
||||
object="Object",
|
||||
description="Description",
|
||||
name="Me",
|
||||
address="Earth",
|
||||
acquitted=False,
|
||||
)
|
||||
self.product = Product.objects.create(
|
||||
invoice=self.invoice,
|
||||
designation="Product",
|
||||
quantity=3,
|
||||
amount=3.14,
|
||||
)
|
||||
|
||||
self.credit = SpecialTransaction.objects.create(
|
||||
source=NoteSpecial.objects.get(special_type="Chèque"),
|
||||
destination=self.user.note,
|
||||
amount=4200,
|
||||
reason="Credit",
|
||||
last_name="TOTO",
|
||||
first_name="Toto",
|
||||
bank="Société générale",
|
||||
)
|
||||
|
||||
self.remittance = Remittance.objects.create(
|
||||
remittance_type=RemittanceType.objects.get(),
|
||||
comment="Test remittance",
|
||||
closed=False,
|
||||
)
|
||||
self.credit.specialtransactionproxy.remittance = self.remittance
|
||||
self.credit.specialtransactionproxy.save()
|
||||
|
||||
self.kfet = Club.objects.get(name="Kfet")
|
||||
self.bde = self.kfet.parent_club
|
||||
|
||||
self.kfet_membership = Membership(
|
||||
user=self.user,
|
||||
club=self.kfet,
|
||||
)
|
||||
self.kfet_membership._force_renew_parent = True
|
||||
self.kfet_membership._soge = True
|
||||
self.kfet_membership.save()
|
||||
|
||||
def test_invoice_api(self):
|
||||
"""
|
||||
Load Invoice API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(InvoiceViewSet, "/api/treasury/invoice/")
|
||||
|
||||
def test_product_api(self):
|
||||
"""
|
||||
Load Product API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(ProductViewSet, "/api/treasury/product/")
|
||||
|
||||
def test_remittance_api(self):
|
||||
"""
|
||||
Load Remittance API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(RemittanceViewSet, "/api/treasury/remittance/")
|
||||
|
||||
def test_remittance_type_api(self):
|
||||
"""
|
||||
Load RemittanceType API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(RemittanceTypeViewSet, "/api/treasury/remittance_type/")
|
||||
|
||||
def test_sogecredit_api(self):
|
||||
"""
|
||||
Load SogeCredit API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(SogeCreditViewSet, "/api/treasury/soge_credit/")
|
||||
|
@ -9,6 +9,7 @@ from tempfile import mkdtemp
|
||||
from crispy_forms.helper import FormHelper
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.exceptions import ValidationError, PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.forms import Form
|
||||
from django.http import HttpResponse
|
||||
@ -65,6 +66,7 @@ class InvoiceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
del form.fields["locked"]
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
ret = super().form_valid(form)
|
||||
|
||||
@ -144,6 +146,7 @@ class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
del form.fields["id"]
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
ret = super().form_valid(form)
|
||||
|
||||
@ -428,7 +431,7 @@ class SogeCreditListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableVi
|
||||
if "valid" not in self.request.GET or not self.request.GET["valid"]:
|
||||
qs = qs.filter(credit_transaction__valid=False)
|
||||
|
||||
return qs[:20]
|
||||
return qs
|
||||
|
||||
|
||||
class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormView, DetailView):
|
||||
@ -439,6 +442,7 @@ class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormVie
|
||||
form_class = Form
|
||||
extra_context = {"title": _("Manage credits from the Société générale")}
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
if "validate" in form.data:
|
||||
self.get_object().validate(True)
|
||||
|
@ -1,7 +1,8 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from api.viewsets import ReadProtectedModelViewSet
|
||||
|
||||
from .serializers import WEIClubSerializer, BusSerializer, BusTeamSerializer, WEIRoleSerializer, \
|
||||
@ -15,11 +16,14 @@ class WEIClubViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `WEIClub` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/wei/club/
|
||||
"""
|
||||
queryset = WEIClub.objects.all()
|
||||
queryset = WEIClub.objects.order_by('id')
|
||||
serializer_class = WEIClubSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
search_fields = ['$name', ]
|
||||
filterset_fields = ['name', 'year', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'year', 'date_start', 'date_end', 'email', 'note__alias__name',
|
||||
'note__alias__normalized_name', 'parent_club', 'parent_club__name', 'require_memberships',
|
||||
'membership_fee_paid', 'membership_fee_unpaid', 'membership_duration', 'membership_start',
|
||||
'membership_end', ]
|
||||
search_fields = ['$name', '$email', '$note__alias__name', '$note__alias__normalized_name', ]
|
||||
|
||||
|
||||
class BusViewSet(ReadProtectedModelViewSet):
|
||||
@ -28,11 +32,11 @@ class BusViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Bus` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/wei/bus/
|
||||
"""
|
||||
queryset = Bus.objects
|
||||
queryset = Bus.objects.order_by('id')
|
||||
serializer_class = BusSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
search_fields = ['$name', ]
|
||||
filterset_fields = ['name', 'wei', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'wei', 'description', ]
|
||||
search_fields = ['$name', '$wei__name', '$description', ]
|
||||
|
||||
|
||||
class BusTeamViewSet(ReadProtectedModelViewSet):
|
||||
@ -41,11 +45,11 @@ class BusTeamViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/wei/team/
|
||||
"""
|
||||
queryset = BusTeam.objects
|
||||
queryset = BusTeam.objects.order_by('id')
|
||||
serializer_class = BusTeamSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
search_fields = ['$name', ]
|
||||
filterset_fields = ['name', 'bus', 'bus__wei', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'bus', 'color', 'description', 'bus__wei', ]
|
||||
search_fields = ['$name', '$bus__name', '$bus__wei__name', '$description', ]
|
||||
|
||||
|
||||
class WEIRoleViewSet(ReadProtectedModelViewSet):
|
||||
@ -54,9 +58,10 @@ class WEIRoleViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `WEIRole` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/wei/role/
|
||||
"""
|
||||
queryset = WEIRole.objects
|
||||
queryset = WEIRole.objects.order_by('id')
|
||||
serializer_class = WEIRoleSerializer
|
||||
filter_backends = [SearchFilter]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'permissions', 'memberships', ]
|
||||
search_fields = ['$name', ]
|
||||
|
||||
|
||||
@ -66,11 +71,17 @@ class WEIRegistrationViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all WEIRegistration objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/wei/registration/
|
||||
"""
|
||||
queryset = WEIRegistration.objects
|
||||
queryset = WEIRegistration.objects.order_by('id')
|
||||
serializer_class = WEIRegistrationSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
search_fields = ['$user__username', ]
|
||||
filterset_fields = ['user', 'wei', ]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['user', 'user__username', 'user__first_name', 'user__last_name', 'user__email',
|
||||
'user__note__alias__name', 'user__note__alias__normalized_name', 'wei', 'wei__name',
|
||||
'wei__email', 'wei__year', 'soge_credit', 'caution_check', 'birth_date', 'gender',
|
||||
'clothing_cut', 'clothing_size', 'first_year', 'emergency_contact_name',
|
||||
'emergency_contact_phone', ]
|
||||
search_fields = ['$user__username', '$user__first_name', '$user__last_name', '$user__email',
|
||||
'$user__note__alias__name', '$user__note__alias__normalized_name', '$wei__name',
|
||||
'$wei__email', '$health_issues', '$emergency_contact_name', '$emergency_contact_phone', ]
|
||||
|
||||
|
||||
class WEIMembershipViewSet(ReadProtectedModelViewSet):
|
||||
@ -79,8 +90,16 @@ class WEIMembershipViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/wei/membership/
|
||||
"""
|
||||
queryset = WEIMembership.objects
|
||||
queryset = WEIMembership.objects.order_by('id')
|
||||
serializer_class = WEIMembershipSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||
search_fields = ['$user__username', '$bus__name', '$team__name', ]
|
||||
filterset_fields = ['user', 'club', 'bus', 'team', ]
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
filterset_fields = ['club__name', 'club__email', 'club__note__alias__name',
|
||||
'club__note__alias__normalized_name', 'user__username', 'user__last_name',
|
||||
'user__first_name', 'user__email', 'user__note__alias__name',
|
||||
'user__note__alias__normalized_name', 'date_start', 'date_end', 'fee', 'roles', 'bus',
|
||||
'bus__name', 'team', 'team__name', 'registration', ]
|
||||
ordering_fields = ['id', 'date_start', 'date_end', ]
|
||||
search_fields = ['$club__name', '$club__email', '$club__note__alias__name',
|
||||
'$club__note__alias__normalized_name', '$user__username', '$user__last_name',
|
||||
'$user__first_name', '$user__email', '$user__note__alias__name',
|
||||
'$user__note__alias__normalized_name', '$roles__name', '$bus__name', '$team__name', ]
|
||||
|
@ -4,6 +4,7 @@
|
||||
from random import choice
|
||||
|
||||
from django import forms
|
||||
from django.db import transaction
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
|
||||
@ -88,6 +89,7 @@ class WEISurvey2020(WEISurvey):
|
||||
"""
|
||||
form.set_registration(self.registration)
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
word = form.cleaned_data["word"]
|
||||
self.information.step += 1
|
||||
|
@ -1,59 +0,0 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import date
|
||||
|
||||
from django.core.management import BaseCommand
|
||||
from django.db.models import Q
|
||||
from member.models import Membership, Club
|
||||
from wei.models import WEIClub
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Get mailing list registrations from the last wei. " \
|
||||
"Usage: manage.py extract_ml_registrations -t {events,art,sport}. " \
|
||||
"You can write this into a file with a pipe, then paste the document into your mail manager."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--type', '-t', choices=["members", "clubs", "events", "art", "sport"], default="members",
|
||||
help='Select the type of the mailing list (default members)')
|
||||
parser.add_argument('--year', '-y', type=int, default=None,
|
||||
help='Select the year of the concerned WEI. Default: last year')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
###########################################################
|
||||
# WARNING #
|
||||
###########################################################
|
||||
#
|
||||
# This code is obsolete.
|
||||
# TODO: Improve the mailing list extraction system, and link it automatically with Mailman.
|
||||
|
||||
if options["type"] == "members":
|
||||
for membership in Membership.objects.filter(
|
||||
club__name="BDE",
|
||||
date_start__lte=date.today(),
|
||||
date_end__gte=date.today(),
|
||||
).all():
|
||||
self.stdout.write(membership.user.email)
|
||||
return
|
||||
|
||||
if options["type"] == "clubs":
|
||||
for club in Club.objects.all():
|
||||
self.stdout.write(club.email)
|
||||
return
|
||||
|
||||
if options["year"] is None:
|
||||
wei = WEIClub.objects.order_by('-year').first()
|
||||
else:
|
||||
wei = WEIClub.objects.filter(year=options["year"])
|
||||
if wei.exists():
|
||||
wei = wei.get()
|
||||
else:
|
||||
wei = WEIClub.objects.order_by('-year').first()
|
||||
self.stderr.write(self.style.WARNING("Warning: there was no WEI in year " + str(options["year"]) + ". "
|
||||
+ "Assuming the last WEI (year " + str(wei.year) + ")"))
|
||||
q = Q(ml_events_registration=True) if options["type"] == "events" else Q(ml_art_registration=True)\
|
||||
if options["type"] == "art" else Q(ml_sport_registration=True)
|
||||
registrations = wei.users.filter(q)
|
||||
for registration in registrations.all():
|
||||
self.stdout.write(registration.user.email)
|
@ -238,7 +238,7 @@ class WEIRegistration(models.Model):
|
||||
information_json = models.TextField(
|
||||
default="{}",
|
||||
verbose_name=_("registration information"),
|
||||
help_text=_("Information about the registration (buses for old members, survey fot the new members), "
|
||||
help_text=_("Information about the registration (buses for old members, survey for the new members), "
|
||||
"encoded in JSON"),
|
||||
)
|
||||
|
||||
|
@ -61,10 +61,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<dd class="col-xl-6">{{ club.note.balance | pretty_money }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if "note.change_alias"|has_perm:club.note.alias_set.first %}
|
||||
{% if "note.change_alias"|has_perm:club.note.alias.first %}
|
||||
<dt class="col-xl-4"><a
|
||||
href="{% url 'member:club_alias' club.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
|
||||
<dd class="col-xl-8 text-truncate">{{ club.note.alias_set.all|join:", " }}</dd>
|
||||
<dd class="col-xl-8 text-truncate">{{ club.note.alias.all|join:", " }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-xl-4">{% trans 'email'|capfirst %}</dt>
|
||||
|
@ -4,16 +4,19 @@
|
||||
import subprocess
|
||||
from datetime import timedelta, date
|
||||
|
||||
from api.tests import TestAPI
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Q
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from member.models import Membership
|
||||
from member.models import Membership, Club
|
||||
from note.models import NoteClub, SpecialTransaction
|
||||
from treasury.models import SogeCredit
|
||||
|
||||
from ..api.views import BusViewSet, BusTeamViewSet, WEIClubViewSet, WEIMembershipViewSet, WEIRegistrationViewSet, \
|
||||
WEIRoleViewSet
|
||||
from ..forms import CurrentSurvey, WEISurveyAlgorithm, WEISurvey
|
||||
from ..models import WEIClub, Bus, BusTeam, WEIRole, WEIRegistration, WEIMembership
|
||||
|
||||
@ -524,7 +527,7 @@ class TestWEIRegistration(TestCase):
|
||||
sess["permission_mask"] = 0
|
||||
sess.save()
|
||||
response = self.client.get(reverse("wei:wei_update_registration", kwargs=dict(pk=self.registration.pk)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
sess["permission_mask"] = 42
|
||||
sess.save()
|
||||
|
||||
@ -807,3 +810,97 @@ class TestWEISurveyAlgorithm(TestCase):
|
||||
|
||||
def test_survey_algorithm(self):
|
||||
CurrentSurvey.get_algorithm_class()().run_algorithm()
|
||||
|
||||
|
||||
class TestWeiAPI(TestAPI):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
self.year = timezone.now().year
|
||||
self.wei = WEIClub.objects.create(
|
||||
name="Test WEI",
|
||||
email="gc.wei@example.com",
|
||||
parent_club_id=2,
|
||||
membership_fee_paid=12500,
|
||||
membership_fee_unpaid=5500,
|
||||
membership_start=date(self.year, 1, 1),
|
||||
membership_end=date(self.year, 12, 31),
|
||||
membership_duration=396,
|
||||
year=self.year,
|
||||
date_start=date.today() + timedelta(days=2),
|
||||
date_end=date(self.year, 12, 31),
|
||||
)
|
||||
NoteClub.objects.create(club=self.wei)
|
||||
self.bus = Bus.objects.create(
|
||||
name="Test Bus",
|
||||
wei=self.wei,
|
||||
description="Test Bus",
|
||||
)
|
||||
self.team = BusTeam.objects.create(
|
||||
name="Test Team",
|
||||
bus=self.bus,
|
||||
color=0xFFFFFF,
|
||||
description="Test Team",
|
||||
)
|
||||
self.registration = WEIRegistration.objects.create(
|
||||
user_id=self.user.id,
|
||||
wei_id=self.wei.id,
|
||||
soge_credit=True,
|
||||
caution_check=True,
|
||||
birth_date=date(2000, 1, 1),
|
||||
gender="nonbinary",
|
||||
clothing_cut="male",
|
||||
clothing_size="XL",
|
||||
health_issues="I am a bot",
|
||||
emergency_contact_name="Pikachu",
|
||||
emergency_contact_phone="+33123456789",
|
||||
first_year=False,
|
||||
)
|
||||
Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE"))
|
||||
Membership.objects.create(user=self.user, club=Club.objects.get(name="Kfet"))
|
||||
self.membership = WEIMembership.objects.create(
|
||||
user=self.user,
|
||||
club=self.wei,
|
||||
fee=125,
|
||||
bus=self.bus,
|
||||
team=self.team,
|
||||
registration=self.registration,
|
||||
)
|
||||
self.membership.roles.add(WEIRole.objects.last())
|
||||
self.membership.save()
|
||||
|
||||
def test_weiclub_api(self):
|
||||
"""
|
||||
Load WEI API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(WEIClubViewSet, "/api/wei/club/")
|
||||
|
||||
def test_wei_bus_api(self):
|
||||
"""
|
||||
Load Bus API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(BusViewSet, "/api/wei/bus/")
|
||||
|
||||
def test_wei_team_api(self):
|
||||
"""
|
||||
Load BusTeam API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(BusTeamViewSet, "/api/wei/team/")
|
||||
|
||||
def test_weirole_api(self):
|
||||
"""
|
||||
Load WEIRole API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(WEIRoleViewSet, "/api/wei/role/")
|
||||
|
||||
def test_weiregistration_api(self):
|
||||
"""
|
||||
Load WEIRegistration API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(WEIRegistrationViewSet, "/api/wei/registration/")
|
||||
|
||||
def test_weimembership_api(self):
|
||||
"""
|
||||
Load WEIMembership API page and test all filters and permissions
|
||||
"""
|
||||
self.check_viewset(WEIMembershipViewSet, "/api/wei/membership/")
|
||||
|
@ -10,6 +10,7 @@ from tempfile import mkdtemp
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.db.models import Q, Count
|
||||
from django.db.models.functions.text import Lower
|
||||
from django.forms import HiddenInput
|
||||
@ -84,6 +85,7 @@ class WEICreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
date_end=date.today(),
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.requires_membership = True
|
||||
form.instance.parent_club = Club.objects.get(name="Kfet")
|
||||
@ -517,6 +519,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
del form.fields["information_json"]
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
|
||||
form.instance.first_year = True
|
||||
@ -597,6 +600,7 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
|
||||
form.instance.first_year = False
|
||||
@ -688,6 +692,7 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
|
||||
del form.fields["information_json"]
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
# If the membership is already validated, then we update the bus and the team (and the roles)
|
||||
if form.instance.is_validated:
|
||||
@ -866,6 +871,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
).all()
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
Create membership, check that all is good, make transactions
|
||||
@ -1016,6 +1022,7 @@ class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
|
||||
context["club"] = self.object.wei
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
Update the survey with the data of the form.
|
||||
|
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
@ -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
@ -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
@ -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
@ -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
@ -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
|
20
docs/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
344
docs/_static/img/graphs/activity.svg
vendored
Normal file
@ -0,0 +1,344 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.44.1 (0)
|
||||
-->
|
||||
<!-- Title: model_graph Pages: 1 -->
|
||||
<svg width="578pt" height="729pt"
|
||||
viewBox="0.00 0.00 578.00 729.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 725)">
|
||||
<title>model_graph</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-725 574,-725 574,4 -4,4"/>
|
||||
<!-- activity_models_ActivityType -->
|
||||
<g id="node1" class="node">
|
||||
<title>activity_models_ActivityType</title>
|
||||
<polygon fill="white" stroke="transparent" points="8,-4 8,-92 186,-92 186,-4 8,-4"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="9,-70 9,-91 185,-91 185,-70 9,-70"/>
|
||||
<text text-anchor="start" x="51" y="-79" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="61" y="-79" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    ActivityType    </text>
|
||||
<text text-anchor="start" x="11" y="-62.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-62.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="29" y="-62.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="93" y="-62.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="103" y="-62.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="141" y="-62.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-49.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-49.6" font-family="Roboto" font-size="8.00">can_invite</text>
|
||||
<text text-anchor="start" x="57" y="-49.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="93" y="-49.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="103" y="-49.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="149" y="-49.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-36.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-36.6" font-family="Roboto" font-size="8.00">guest_entry_fee</text>
|
||||
<text text-anchor="start" x="79" y="-36.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="93" y="-36.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="103" y="-36.6" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="173" y="-36.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-23.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-23.6" font-family="Roboto" font-size="8.00">manage_entries</text>
|
||||
<text text-anchor="start" x="79" y="-23.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="93" y="-23.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="103" y="-23.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="149" y="-23.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-10.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-10.6" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="42" y="-10.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="93" y="-10.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="103" y="-10.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="138" y="-10.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="8,-4 8,-92 186,-92 186,-4 8,-4"/>
|
||||
</g>
|
||||
<!-- activity_models_Activity -->
|
||||
<g id="node2" class="node">
|
||||
<title>activity_models_Activity</title>
|
||||
<polygon fill="white" stroke="transparent" points="197,-145 197,-324 369,-324 369,-145 197,-145"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="198,-301.5 198,-322.5 368,-322.5 368,-301.5 198,-301.5"/>
|
||||
<text text-anchor="start" x="248" y="-310.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="258" y="-310.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Activity    </text>
|
||||
<text text-anchor="start" x="200" y="-294.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-294.1" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="218" y="-294.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-294.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-294.1" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="333" y="-294.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-281.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-281.1" font-family="Roboto" font-weight="bold" font-size="8.00">activity_type</text>
|
||||
<text text-anchor="start" x="261" y="-281.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-281.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-281.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="356" y="-281.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-268.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-268.1" font-family="Roboto" font-weight="bold" font-size="8.00">attendees_club</text>
|
||||
<text text-anchor="start" x="271" y="-268.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-268.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-268.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="356" y="-268.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-255.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-255.1" font-family="Roboto" font-weight="bold" font-size="8.00">creater</text>
|
||||
<text text-anchor="start" x="238" y="-255.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-255.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-255.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="356" y="-255.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-242.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-242.1" font-family="Roboto" font-weight="bold" font-size="8.00">organizer</text>
|
||||
<text text-anchor="start" x="247" y="-242.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-242.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-242.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="356" y="-242.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-229.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-229.1" font-family="Roboto" font-size="8.00">date_end</text>
|
||||
<text text-anchor="start" x="244" y="-229.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-229.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-229.1" font-family="Roboto" font-size="8.00">DateTimeField</text>
|
||||
<text text-anchor="start" x="347" y="-229.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-216.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-216.1" font-family="Roboto" font-size="8.00">date_start</text>
|
||||
<text text-anchor="start" x="247" y="-216.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-216.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-216.1" font-family="Roboto" font-size="8.00">DateTimeField</text>
|
||||
<text text-anchor="start" x="347" y="-216.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-203.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-203.1" font-family="Roboto" font-size="8.00">description</text>
|
||||
<text text-anchor="start" x="249" y="-203.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-203.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-203.1" font-family="Roboto" font-size="8.00">TextField</text>
|
||||
<text text-anchor="start" x="327" y="-203.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-190.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-190.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">location</text>
|
||||
<text text-anchor="start" x="238" y="-190.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-190.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-190.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="330" y="-190.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-177.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-177.1" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="231" y="-177.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-177.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-177.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="330" y="-177.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-164.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-164.1" font-family="Roboto" font-size="8.00">open</text>
|
||||
<text text-anchor="start" x="229" y="-164.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-164.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-164.1" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="341" y="-164.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="200" y="-151.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="210" y="-151.1" font-family="Roboto" font-size="8.00">valid</text>
|
||||
<text text-anchor="start" x="226" y="-151.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="285" y="-151.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="295" y="-151.1" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="341" y="-151.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="197,-145 197,-324 369,-324 369,-145 197,-145"/>
|
||||
</g>
|
||||
<!-- activity_models_Activity->activity_models_ActivityType -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>activity_models_Activity->activity_models_ActivityType</title>
|
||||
<path fill="none" stroke="black" d="M183.88,-135.18C170.27,-121.68 156.83,-108.35 144.74,-96.35"/>
|
||||
<ellipse fill="black" stroke="black" cx="186.89" cy="-138.16" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="197" y="-116.6" font-family="Roboto" font-size="8.00"> activity_type (+)</text>
|
||||
</g>
|
||||
<!-- django_contrib_auth_models_User -->
|
||||
<g id="node6" class="node">
|
||||
<title>django_contrib_auth_models_User</title>
|
||||
<polygon fill="white" stroke="transparent" points="220,-37.5 220,-58.5 264,-58.5 264,-37.5 220,-37.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="220,-37 220,-58 264,-58 264,-37 220,-37"/>
|
||||
<text text-anchor="start" x="224" y="-45.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="229" y="-45.4" font-family="Roboto" font-size="12.00" fill="white">User</text>
|
||||
<text text-anchor="start" x="255" y="-45.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- activity_models_Activity->django_contrib_auth_models_User -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>activity_models_Activity->django_contrib_auth_models_User</title>
|
||||
<path fill="none" stroke="black" d="M245.08,-133.07C244.3,-129.69 243.61,-126.33 243,-123 239.5,-103.79 239.8,-81.3 240.63,-66.2"/>
|
||||
<ellipse fill="black" stroke="black" cx="246.06" cy="-137.08" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="272.5" y="-116.6" font-family="Roboto" font-size="8.00"> creater (activity)</text>
|
||||
</g>
|
||||
<!-- member_models_Club -->
|
||||
<g id="node7" class="node">
|
||||
<title>member_models_Club</title>
|
||||
<polygon fill="white" stroke="transparent" points="338,-37.5 338,-58.5 382,-58.5 382,-37.5 338,-37.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="338,-37 338,-58 382,-58 382,-37 338,-37"/>
|
||||
<text text-anchor="start" x="342" y="-45.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="347" y="-45.4" font-family="Roboto" font-size="12.00" fill="white">Club</text>
|
||||
<text text-anchor="start" x="373" y="-45.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- activity_models_Activity->member_models_Club -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>activity_models_Activity->member_models_Club</title>
|
||||
<path fill="none" stroke="black" d="M316.94,-133.22C319.56,-126.67 322.25,-120.22 325,-114 332.41,-97.23 342.46,-78.92 349.89,-66.01"/>
|
||||
<ellipse fill="black" stroke="black" cx="315.37" cy="-137.22" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="349.5" y="-116.6" font-family="Roboto" font-size="8.00"> organizer (+)</text>
|
||||
</g>
|
||||
<!-- activity_models_Activity->member_models_Club -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>activity_models_Activity->member_models_Club</title>
|
||||
<path fill="none" stroke="black" d="M369.62,-133.72C371.23,-130.19 372.7,-126.61 374,-123 380.76,-104.17 374.44,-81.42 368.26,-66.15"/>
|
||||
<ellipse fill="black" stroke="black" cx="367.85" cy="-137.32" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="412" y="-116.6" font-family="Roboto" font-size="8.00"> attendees_club (+)</text>
|
||||
</g>
|
||||
<!-- activity_models_Entry -->
|
||||
<g id="node3" class="node">
|
||||
<title>activity_models_Entry</title>
|
||||
<polygon fill="white" stroke="transparent" points="284,-518 284,-606 448,-606 448,-518 284,-518"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="285,-584 285,-605 447,-605 447,-584 285,-584"/>
|
||||
<text text-anchor="start" x="336.5" y="-593" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="346.5" y="-593" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Entry    </text>
|
||||
<text text-anchor="start" x="287" y="-576.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="297" y="-576.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="305" y="-576.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="340" y="-576.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="350" y="-576.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="388" y="-576.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="287" y="-563.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="297" y="-563.6" font-family="Roboto" font-weight="bold" font-size="8.00">activity</text>
|
||||
<text text-anchor="start" x="326" y="-563.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="340" y="-563.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="350" y="-563.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="411" y="-563.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="287" y="-550.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="297" y="-550.6" font-family="Roboto" font-weight="bold" font-size="8.00">guest</text>
|
||||
<text text-anchor="start" x="320" y="-550.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="340" y="-550.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="350" y="-550.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="426" y="-550.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="287" y="-537.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="297" y="-537.6" font-family="Roboto" font-weight="bold" font-size="8.00">note</text>
|
||||
<text text-anchor="start" x="315" y="-537.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="340" y="-537.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="350" y="-537.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (note_ptr)</text>
|
||||
<text text-anchor="start" x="435" y="-537.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="287" y="-524.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="297" y="-524.6" font-family="Roboto" font-size="8.00">time</text>
|
||||
<text text-anchor="start" x="313" y="-524.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="340" y="-524.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="350" y="-524.6" font-family="Roboto" font-size="8.00">DateTimeField</text>
|
||||
<text text-anchor="start" x="402" y="-524.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="284,-518 284,-606 448,-606 448,-518 284,-518"/>
|
||||
</g>
|
||||
<!-- activity_models_Entry->activity_models_Activity -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>activity_models_Entry->activity_models_Activity</title>
|
||||
<path fill="none" stroke="black" d="M268.49,-526.87C243.03,-513.01 218.65,-494.17 204,-469 178.65,-425.45 193.33,-372.56 216.59,-328.14"/>
|
||||
<ellipse fill="black" stroke="black" cx="272.15" cy="-528.79" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="233" y="-419.1" font-family="Roboto" font-size="8.00"> activity (entries)</text>
|
||||
</g>
|
||||
<!-- activity_models_Guest -->
|
||||
<g id="node4" class="node">
|
||||
<title>activity_models_Guest</title>
|
||||
<polygon fill="white" stroke="transparent" points="279.5,-377 279.5,-465 452.5,-465 452.5,-377 279.5,-377"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="281,-443 281,-464 452,-464 452,-443 281,-443"/>
|
||||
<text text-anchor="start" x="335.5" y="-452" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="345.5" y="-452" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Guest    </text>
|
||||
<text text-anchor="start" x="283" y="-435.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="293" y="-435.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="301" y="-435.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="345" y="-435.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="355" y="-435.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="393" y="-435.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="283" y="-422.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="293" y="-422.6" font-family="Roboto" font-weight="bold" font-size="8.00">activity</text>
|
||||
<text text-anchor="start" x="322" y="-422.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="345" y="-422.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="355" y="-422.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="416" y="-422.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="283" y="-409.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="293" y="-409.6" font-family="Roboto" font-weight="bold" font-size="8.00">inviter</text>
|
||||
<text text-anchor="start" x="318" y="-409.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="345" y="-409.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="355" y="-409.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (note_ptr)</text>
|
||||
<text text-anchor="start" x="440" y="-409.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="283" y="-396.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="293" y="-396.6" font-family="Roboto" font-size="8.00">first_name</text>
|
||||
<text text-anchor="start" x="331" y="-396.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="345" y="-396.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="355" y="-396.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="390" y="-396.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="283" y="-383.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="293" y="-383.6" font-family="Roboto" font-size="8.00">last_name</text>
|
||||
<text text-anchor="start" x="330" y="-383.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="345" y="-383.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="355" y="-383.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="390" y="-383.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="279.5,-377 279.5,-465 452.5,-465 452.5,-377 279.5,-377"/>
|
||||
</g>
|
||||
<!-- activity_models_Entry->activity_models_Guest -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>activity_models_Entry->activity_models_Guest</title>
|
||||
<path fill="none" stroke="black" d="M366,-513.94C366,-499.45 366,-483.5 366,-469.01"/>
|
||||
<text text-anchor="middle" x="390.5" y="-489.6" font-family="Roboto" font-size="8.00"> guest (entry)</text>
|
||||
</g>
|
||||
<!-- note_models_notes_NoteUser -->
|
||||
<g id="node8" class="node">
|
||||
<title>note_models_notes_NoteUser</title>
|
||||
<polygon fill="white" stroke="transparent" points="411,-224 411,-245 481,-245 481,-224 411,-224"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="411,-223.5 411,-244.5 481,-244.5 481,-223.5 411,-223.5"/>
|
||||
<text text-anchor="start" x="415" y="-231.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="420" y="-231.9" font-family="Roboto" font-size="12.00" fill="white">NoteUser</text>
|
||||
<text text-anchor="start" x="472" y="-231.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- activity_models_Entry->note_models_notes_NoteUser -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>activity_models_Entry->note_models_notes_NoteUser</title>
|
||||
<path fill="none" stroke="black" d="M442.25,-507.94C453.52,-496.48 463.51,-483.41 470,-469 503.73,-394.13 469.49,-292.46 453.39,-252.56"/>
|
||||
<ellipse fill="black" stroke="black" cx="439.12" cy="-510.99" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="506.5" y="-419.1" font-family="Roboto" font-size="8.00"> note (entry)</text>
|
||||
</g>
|
||||
<!-- activity_models_Guest->activity_models_Activity -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>activity_models_Guest->activity_models_Activity</title>
|
||||
<path fill="none" stroke="black" d="M341.43,-365.38C336.13,-353.6 330.39,-340.84 324.64,-328.06"/>
|
||||
<ellipse fill="black" stroke="black" cx="343.2" cy="-369.32" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="354.5" y="-348.6" font-family="Roboto" font-size="8.00"> activity (+)</text>
|
||||
</g>
|
||||
<!-- activity_models_Guest->note_models_notes_NoteUser -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>activity_models_Guest->note_models_notes_NoteUser</title>
|
||||
<path fill="none" stroke="black" d="M389.66,-365.44C406.3,-327.05 427.51,-278.15 438.55,-252.68"/>
|
||||
<ellipse fill="black" stroke="black" cx="387.99" cy="-369.3" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="424.5" y="-348.6" font-family="Roboto" font-size="8.00"> inviter (guests)</text>
|
||||
</g>
|
||||
<!-- activity_models_GuestTransaction -->
|
||||
<g id="node5" class="node">
|
||||
<title>activity_models_GuestTransaction</title>
|
||||
<polygon fill="white" stroke="transparent" points="350.5,-668 350.5,-717 537.5,-717 537.5,-668 350.5,-668"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="352,-694.5 352,-715.5 537,-715.5 537,-694.5 352,-694.5"/>
|
||||
<text text-anchor="start" x="386.5" y="-703.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="396.5" y="-703.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    GuestTransaction    </text>
|
||||
<text text-anchor="start" x="354" y="-687.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="364" y="-687.1" font-family="Roboto" font-weight="bold" font-size="8.00">transaction_ptr</text>
|
||||
<text text-anchor="start" x="425" y="-687.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="439" y="-687.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="449" y="-687.1" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="525" y="-687.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="354" y="-674.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="364" y="-674.1" font-family="Roboto" font-weight="bold" font-size="8.00">entry</text>
|
||||
<text text-anchor="start" x="385" y="-674.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="439" y="-674.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="449" y="-674.1" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="525" y="-674.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="350.5,-668 350.5,-717 537.5,-717 537.5,-668 350.5,-668"/>
|
||||
</g>
|
||||
<!-- activity_models_GuestTransaction->activity_models_Entry -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>activity_models_GuestTransaction->activity_models_Entry</title>
|
||||
<path fill="none" stroke="black" d="M423.89,-663.91C419.84,-658.07 415.69,-651.89 412,-646 404.83,-634.55 397.53,-621.92 390.93,-610.04"/>
|
||||
<text text-anchor="middle" x="456" y="-635.1" font-family="Roboto" font-size="8.00"> entry (guesttransaction)</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_Transaction -->
|
||||
<g id="node9" class="node">
|
||||
<title>note_models_transactions_Transaction</title>
|
||||
<polygon fill="white" stroke="transparent" points="482,-551.5 482,-572.5 562,-572.5 562,-551.5 482,-551.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="482,-551 482,-572 562,-572 562,-551 482,-551"/>
|
||||
<text text-anchor="start" x="486" y="-559.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="491" y="-559.4" font-family="Roboto" font-size="12.00" fill="white">Transaction</text>
|
||||
<text text-anchor="start" x="553" y="-559.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- activity_models_GuestTransaction->note_models_transactions_Transaction -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>activity_models_GuestTransaction->note_models_transactions_Transaction</title>
|
||||
<path fill="none" stroke="black" d="M484.47,-663.95C490.33,-658.55 495.78,-652.52 500,-646 510.86,-629.2 516.38,-607.22 519.17,-590.09"/>
|
||||
<polygon fill="none" stroke="black" points="522.64,-590.54 520.58,-580.15 515.71,-589.56 522.64,-590.54"/>
|
||||
<text text-anchor="middle" x="528" y="-639.6" font-family="Roboto" font-size="8.00"> multi-table</text>
|
||||
<text text-anchor="middle" x="528" y="-630.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 30 KiB |
108
docs/_static/img/graphs/logs.svg
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.44.1 (0)
|
||||
-->
|
||||
<!-- Title: model_graph Pages: 1 -->
|
||||
<svg width="209pt" height="237pt"
|
||||
viewBox="0.00 0.00 208.50 237.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 233)">
|
||||
<title>model_graph</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-233 204.5,-233 204.5,4 -4,4"/>
|
||||
<!-- logs_models_Changelog -->
|
||||
<g id="node1" class="node">
|
||||
<title>logs_models_Changelog</title>
|
||||
<polygon fill="white" stroke="transparent" points="8,-85 8,-225 181,-225 181,-85 8,-85"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="9.5,-203 9.5,-224 180.5,-224 180.5,-203 9.5,-203"/>
|
||||
<text text-anchor="start" x="52" y="-212" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="62" y="-212" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Changelog    </text>
|
||||
<text text-anchor="start" x="11.5" y="-195.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-195.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="29.5" y="-195.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-195.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="88.5" y="-195.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="126.5" y="-195.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-182.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-182.6" font-family="Roboto" font-weight="bold" font-size="8.00">model</text>
|
||||
<text text-anchor="start" x="46.5" y="-182.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-182.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="88.5" y="-182.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="149.5" y="-182.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-169.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-169.6" font-family="Roboto" font-weight="bold" font-size="8.00">user</text>
|
||||
<text text-anchor="start" x="39.5" y="-169.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-169.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="88.5" y="-169.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="149.5" y="-169.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-156.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-156.6" font-family="Roboto" font-size="8.00">action</text>
|
||||
<text text-anchor="start" x="43.5" y="-156.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-156.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="88.5" y="-156.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="123.5" y="-156.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-143.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-143.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">data</text>
|
||||
<text text-anchor="start" x="37.5" y="-143.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-143.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="88.5" y="-143.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">TextField</text>
|
||||
<text text-anchor="start" x="120.5" y="-143.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-130.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-130.6" font-family="Roboto" font-size="8.00">instance_pk</text>
|
||||
<text text-anchor="start" x="64.5" y="-130.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-130.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="88.5" y="-130.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="123.5" y="-130.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-117.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-117.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">ip</text>
|
||||
<text text-anchor="start" x="28.5" y="-117.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-117.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="88.5" y="-117.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">GenericIPAddressField</text>
|
||||
<text text-anchor="start" x="168.5" y="-117.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-104.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-104.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">previous</text>
|
||||
<text text-anchor="start" x="51.5" y="-104.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-104.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="88.5" y="-104.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">TextField</text>
|
||||
<text text-anchor="start" x="120.5" y="-104.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-91.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-91.6" font-family="Roboto" font-size="8.00">timestamp</text>
|
||||
<text text-anchor="start" x="58.5" y="-91.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-91.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="88.5" y="-91.6" font-family="Roboto" font-size="8.00">DateTimeField</text>
|
||||
<text text-anchor="start" x="140.5" y="-91.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="8,-85 8,-225 181,-225 181,-85 8,-85"/>
|
||||
</g>
|
||||
<!-- django_contrib_auth_models_User -->
|
||||
<g id="node2" class="node">
|
||||
<title>django_contrib_auth_models_User</title>
|
||||
<polygon fill="white" stroke="transparent" points="23.5,-7.5 23.5,-28.5 67.5,-28.5 67.5,-7.5 23.5,-7.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="23.5,-7 23.5,-28 67.5,-28 67.5,-7 23.5,-7"/>
|
||||
<text text-anchor="start" x="27.5" y="-15.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="32.5" y="-15.4" font-family="Roboto" font-size="12.00" fill="white">User</text>
|
||||
<text text-anchor="start" x="58.5" y="-15.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- logs_models_Changelog->django_contrib_auth_models_User -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>logs_models_Changelog->django_contrib_auth_models_User</title>
|
||||
<path fill="none" stroke="black" d="M62.09,-73.17C60.86,-69.74 59.65,-66.34 58.5,-63 55.49,-54.26 52.54,-44.41 50.21,-36.25"/>
|
||||
<ellipse fill="black" stroke="black" cx="63.48" cy="-76.95" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="90.5" y="-56.6" font-family="Roboto" font-size="8.00"> user (changelog)</text>
|
||||
</g>
|
||||
<!-- django_contrib_contenttypes_models_ContentType -->
|
||||
<g id="node3" class="node">
|
||||
<title>django_contrib_contenttypes_models_ContentType</title>
|
||||
<polygon fill="white" stroke="transparent" points="101.5,-7.5 101.5,-28.5 187.5,-28.5 187.5,-7.5 101.5,-7.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="101.5,-7 101.5,-28 187.5,-28 187.5,-7 101.5,-7"/>
|
||||
<text text-anchor="start" x="105.5" y="-15.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="110.5" y="-15.4" font-family="Roboto" font-size="12.00" fill="white">ContentType</text>
|
||||
<text text-anchor="start" x="178.5" y="-15.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- logs_models_Changelog->django_contrib_contenttypes_models_ContentType -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>logs_models_Changelog->django_contrib_contenttypes_models_ContentType</title>
|
||||
<path fill="none" stroke="black" d="M124.44,-73.16C129.61,-59.21 134.48,-46.05 138.13,-36.2"/>
|
||||
<ellipse fill="black" stroke="black" cx="122.94" cy="-77.22" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="165.5" y="-56.6" font-family="Roboto" font-size="8.00"> model (changelog)</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.3 KiB |
279
docs/_static/img/graphs/member.svg
vendored
Normal file
@ -0,0 +1,279 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.44.1 (0)
|
||||
-->
|
||||
<!-- Title: model_graph Pages: 1 -->
|
||||
<svg width="601pt" height="440pt"
|
||||
viewBox="0.00 0.00 600.50 440.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 436)">
|
||||
<title>model_graph</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-436 596.5,-436 596.5,4 -4,4"/>
|
||||
<!-- member_models_Profile -->
|
||||
<g id="node1" class="node">
|
||||
<title>member_models_Profile</title>
|
||||
<polygon fill="white" stroke="transparent" points="8,-210 8,-428 227,-428 227,-210 8,-210"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="9.5,-406 9.5,-427 226.5,-427 226.5,-406 9.5,-406"/>
|
||||
<text text-anchor="start" x="85.5" y="-415" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="95.5" y="-415" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Profile    </text>
|
||||
<text text-anchor="start" x="11.5" y="-398.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-398.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="29.5" y="-398.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-398.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-398.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="163.5" y="-398.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-385.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-385.6" font-family="Roboto" font-weight="bold" font-size="8.00">user</text>
|
||||
<text text-anchor="start" x="39.5" y="-385.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-385.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-385.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="201.5" y="-385.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-372.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-372.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">address</text>
|
||||
<text text-anchor="start" x="49.5" y="-372.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-372.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-372.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="160.5" y="-372.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-359.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-359.6" font-family="Roboto" font-size="8.00">department</text>
|
||||
<text text-anchor="start" x="63.5" y="-359.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-359.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-359.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="160.5" y="-359.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-346.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-346.6" font-family="Roboto" font-size="8.00">email_confirmed</text>
|
||||
<text text-anchor="start" x="80.5" y="-346.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-346.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-346.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="171.5" y="-346.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-333.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-333.6" font-family="Roboto" font-size="8.00">last_report</text>
|
||||
<text text-anchor="start" x="59.5" y="-333.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-333.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-333.6" font-family="Roboto" font-size="8.00">DateTimeField</text>
|
||||
<text text-anchor="start" x="177.5" y="-333.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-320.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-320.6" font-family="Roboto" font-size="8.00">ml_art_registration</text>
|
||||
<text text-anchor="start" x="88.5" y="-320.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-320.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-320.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="171.5" y="-320.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-307.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-307.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">ml_events_registration</text>
|
||||
<text text-anchor="start" x="101.5" y="-307.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-307.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-307.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="160.5" y="-307.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-294.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-294.6" font-family="Roboto" font-size="8.00">ml_sport_registration</text>
|
||||
<text text-anchor="start" x="96.5" y="-294.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-294.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-294.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="171.5" y="-294.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-281.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-281.6" font-family="Roboto" font-size="8.00">paid</text>
|
||||
<text text-anchor="start" x="37.5" y="-281.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-281.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-281.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="171.5" y="-281.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-268.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-268.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">phone_number</text>
|
||||
<text text-anchor="start" x="76.5" y="-268.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-268.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-268.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">PhoneNumberField</text>
|
||||
<text text-anchor="start" x="195.5" y="-268.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-255.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-255.6" font-family="Roboto" font-size="8.00">promotion</text>
|
||||
<text text-anchor="start" x="57.5" y="-255.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-255.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-255.6" font-family="Roboto" font-size="8.00">PositiveSmallIntegerField</text>
|
||||
<text text-anchor="start" x="214.5" y="-255.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-242.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-242.6" font-family="Roboto" font-size="8.00">registration_valid</text>
|
||||
<text text-anchor="start" x="81.5" y="-242.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-242.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-242.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="171.5" y="-242.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-229.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-229.6" font-family="Roboto" font-size="8.00">report_frequency</text>
|
||||
<text text-anchor="start" x="82.5" y="-229.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-229.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-229.6" font-family="Roboto" font-size="8.00">PositiveSmallIntegerField</text>
|
||||
<text text-anchor="start" x="214.5" y="-229.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-216.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-216.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">section</text>
|
||||
<text text-anchor="start" x="46.5" y="-216.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.5" y="-216.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.5" y="-216.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="160.5" y="-216.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="8,-210 8,-428 227,-428 227,-210 8,-210"/>
|
||||
</g>
|
||||
<!-- django_contrib_auth_models_User -->
|
||||
<g id="node4" class="node">
|
||||
<title>django_contrib_auth_models_User</title>
|
||||
<polygon fill="white" stroke="transparent" points="125.5,-70 125.5,-91 169.5,-91 169.5,-70 125.5,-70"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="125.5,-69.5 125.5,-90.5 169.5,-90.5 169.5,-69.5 125.5,-69.5"/>
|
||||
<text text-anchor="start" x="129.5" y="-77.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="134.5" y="-77.9" font-family="Roboto" font-size="12.00" fill="white">User</text>
|
||||
<text text-anchor="start" x="160.5" y="-77.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- member_models_Profile->django_contrib_auth_models_User -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>member_models_Profile->django_contrib_auth_models_User</title>
|
||||
<path fill="none" stroke="black" d="M131.71,-205.98C136.98,-164.44 142.38,-121.86 145.3,-98.85"/>
|
||||
<text text-anchor="middle" x="158.5" y="-181.6" font-family="Roboto" font-size="8.00"> user (profile)</text>
|
||||
</g>
|
||||
<!-- member_models_Club -->
|
||||
<g id="node2" class="node">
|
||||
<title>member_models_Club</title>
|
||||
<polygon fill="white" stroke="transparent" points="212,-4 212,-157 421,-157 421,-4 212,-4"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="213.5,-134.5 213.5,-155.5 420.5,-155.5 420.5,-134.5 213.5,-134.5"/>
|
||||
<text text-anchor="start" x="288.5" y="-143.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="298.5" y="-143.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Club    </text>
|
||||
<text text-anchor="start" x="215.5" y="-127.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-127.1" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="233.5" y="-127.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="328.5" y="-127.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="338.5" y="-127.1" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="376.5" y="-127.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="215.5" y="-114.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-114.1" font-family="Roboto" font-weight="bold" font-size="8.00" fill="#7b7b7b">parent_club</text>
|
||||
<text text-anchor="start" x="273.5" y="-114.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="328.5" y="-114.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="338.5" y="-114.1" font-family="Roboto" font-weight="bold" font-size="8.00" fill="#7b7b7b">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="399.5" y="-114.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="215.5" y="-101.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-101.1" font-family="Roboto" font-size="8.00">email</text>
|
||||
<text text-anchor="start" x="244.5" y="-101.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="328.5" y="-101.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="338.5" y="-101.1" font-family="Roboto" font-size="8.00">EmailField</text>
|
||||
<text text-anchor="start" x="375.5" y="-101.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="215.5" y="-88.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-88.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">membership_duration</text>
|
||||
<text text-anchor="start" x="304.5" y="-88.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="328.5" y="-88.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="338.5" y="-88.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="408.5" y="-88.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="215.5" y="-75.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-75.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">membership_end</text>
|
||||
<text text-anchor="start" x="288.5" y="-75.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="328.5" y="-75.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="338.5" y="-75.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">DateField</text>
|
||||
<text text-anchor="start" x="372.5" y="-75.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="215.5" y="-62.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-62.1" font-family="Roboto" font-size="8.00">membership_fee_paid</text>
|
||||
<text text-anchor="start" x="305.5" y="-62.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="328.5" y="-62.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="338.5" y="-62.1" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="408.5" y="-62.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="215.5" y="-49.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-49.1" font-family="Roboto" font-size="8.00">membership_fee_unpaid</text>
|
||||
<text text-anchor="start" x="314.5" y="-49.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="328.5" y="-49.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="338.5" y="-49.1" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="408.5" y="-49.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="215.5" y="-36.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-36.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">membership_start</text>
|
||||
<text text-anchor="start" x="290.5" y="-36.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="328.5" y="-36.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="338.5" y="-36.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">DateField</text>
|
||||
<text text-anchor="start" x="372.5" y="-36.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="215.5" y="-23.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-23.1" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="246.5" y="-23.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="328.5" y="-23.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="338.5" y="-23.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="373.5" y="-23.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="215.5" y="-10.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-10.1" font-family="Roboto" font-size="8.00">require_memberships</text>
|
||||
<text text-anchor="start" x="302.5" y="-10.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="328.5" y="-10.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="338.5" y="-10.1" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="384.5" y="-10.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="212,-4 212,-157 421,-157 421,-4 212,-4"/>
|
||||
</g>
|
||||
<!-- member_models_Club->member_models_Club -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>member_models_Club->member_models_Club</title>
|
||||
<path fill="none" stroke="black" d="M437.02,-93.04C443.29,-89.95 447,-85.77 447,-80.5 447,-73.33 440.12,-68.17 429.26,-65.04"/>
|
||||
<ellipse fill="black" stroke="black" cx="433" cy="-94.55" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="480.5" y="-78.6" font-family="Roboto" font-size="8.00"> parent_club (club)</text>
|
||||
</g>
|
||||
<!-- member_models_Membership -->
|
||||
<g id="node3" class="node">
|
||||
<title>member_models_Membership</title>
|
||||
<polygon fill="white" stroke="transparent" points="261,-268.5 261,-369.5 418,-369.5 418,-268.5 261,-268.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="262.5,-347 262.5,-368 417.5,-368 417.5,-347 262.5,-347"/>
|
||||
<text text-anchor="start" x="294" y="-356" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304" y="-356" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Membership    </text>
|
||||
<text text-anchor="start" x="264.5" y="-339.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="274.5" y="-339.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="282.5" y="-339.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="325.5" y="-339.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="335.5" y="-339.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="373.5" y="-339.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="264.5" y="-326.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="274.5" y="-326.6" font-family="Roboto" font-weight="bold" font-size="8.00">club</text>
|
||||
<text text-anchor="start" x="292.5" y="-326.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="325.5" y="-326.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="335.5" y="-326.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="396.5" y="-326.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="264.5" y="-313.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="274.5" y="-313.6" font-family="Roboto" font-weight="bold" font-size="8.00">user</text>
|
||||
<text text-anchor="start" x="292.5" y="-313.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="325.5" y="-313.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="335.5" y="-313.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="396.5" y="-313.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="264.5" y="-300.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="274.5" y="-300.6" font-family="Roboto" font-size="8.00">date_end</text>
|
||||
<text text-anchor="start" x="308.5" y="-300.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="325.5" y="-300.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="335.5" y="-300.6" font-family="Roboto" font-size="8.00">DateField</text>
|
||||
<text text-anchor="start" x="369.5" y="-300.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="264.5" y="-287.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="274.5" y="-287.6" font-family="Roboto" font-size="8.00">date_start</text>
|
||||
<text text-anchor="start" x="311.5" y="-287.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="325.5" y="-287.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="335.5" y="-287.6" font-family="Roboto" font-size="8.00">DateField</text>
|
||||
<text text-anchor="start" x="369.5" y="-287.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="264.5" y="-274.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="274.5" y="-274.6" font-family="Roboto" font-size="8.00">fee</text>
|
||||
<text text-anchor="start" x="286.5" y="-274.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="325.5" y="-274.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="335.5" y="-274.6" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="405.5" y="-274.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="261,-268.5 261,-369.5 418,-369.5 418,-268.5 261,-268.5"/>
|
||||
</g>
|
||||
<!-- member_models_Membership->member_models_Club -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>member_models_Membership->member_models_Club</title>
|
||||
<path fill="none" stroke="black" d="M333.5,-256.33C330.68,-227.34 327.29,-192.43 324.27,-161.45"/>
|
||||
<ellipse fill="black" stroke="black" cx="333.89" cy="-260.35" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="362.5" y="-181.6" font-family="Roboto" font-size="8.00"> club (membership)</text>
|
||||
</g>
|
||||
<!-- member_models_Membership->django_contrib_auth_models_User -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>member_models_Membership->django_contrib_auth_models_User</title>
|
||||
<path fill="none" stroke="black" d="M290.49,-258.16C275.79,-240.95 259.35,-222.4 243.5,-206 222.95,-184.74 213.11,-183.97 194.5,-161 178.45,-141.19 164.21,-115.32 155.72,-98.56"/>
|
||||
<ellipse fill="black" stroke="black" cx="293.24" cy="-261.39" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="261" y="-181.6" font-family="Roboto" font-size="8.00"> user (memberships)</text>
|
||||
</g>
|
||||
<!-- permission_models_Role -->
|
||||
<g id="node5" class="node">
|
||||
<title>permission_models_Role</title>
|
||||
<polygon fill="white" stroke="transparent" points="540.5,-70 540.5,-91 584.5,-91 584.5,-70 540.5,-70"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="540.5,-69.5 540.5,-90.5 584.5,-90.5 584.5,-69.5 540.5,-69.5"/>
|
||||
<text text-anchor="start" x="545" y="-77.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="550" y="-77.9" font-family="Roboto" font-size="12.00" fill="white">Role</text>
|
||||
<text text-anchor="start" x="575" y="-77.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- member_models_Membership->permission_models_Role -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>member_models_Membership->permission_models_Role</title>
|
||||
<path fill="none" stroke="black" d="M422.13,-259.45C456.46,-232.28 494.89,-197.84 523.5,-161 536.38,-144.41 546.62,-122.88 553.37,-106.41"/>
|
||||
<ellipse fill="black" stroke="black" cx="418.91" cy="-261.97" rx="4" ry="4"/>
|
||||
<ellipse fill="black" stroke="black" cx="554.93" cy="-102.49" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="542.5" y="-181.6" font-family="Roboto" font-size="8.00"> roles (membership)</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 26 KiB |
588
docs/_static/img/graphs/note.svg
vendored
Normal file
@ -0,0 +1,588 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.44.1 (0)
|
||||
-->
|
||||
<!-- Title: model_graph Pages: 1 -->
|
||||
<svg width="1306pt" height="872pt"
|
||||
viewBox="0.00 0.00 1306.00 872.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 868)">
|
||||
<title>model_graph</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-868 1302,-868 1302,4 -4,4"/>
|
||||
<!-- polymorphic_models_PolymorphicModel -->
|
||||
<g id="node1" class="node">
|
||||
<title>polymorphic_models_PolymorphicModel</title>
|
||||
<polygon fill="white" stroke="transparent" points="254,-85 254,-121 441,-121 441,-85 254,-85"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="255.5,-99 255.5,-120 440.5,-120 440.5,-99 255.5,-99"/>
|
||||
<text text-anchor="start" x="286.5" y="-108" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="296.5" y="-108" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    PolymorphicModel    </text>
|
||||
<text text-anchor="start" x="257.5" y="-91.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="267.5" y="-91.6" font-family="Roboto" font-weight="bold" font-size="8.00">polymorphic_ctype</text>
|
||||
<text text-anchor="start" x="343.5" y="-91.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="357.5" y="-91.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="367.5" y="-91.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="428.5" y="-91.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="254,-85 254,-121 441,-121 441,-85 254,-85"/>
|
||||
</g>
|
||||
<!-- django_contrib_contenttypes_models_ContentType -->
|
||||
<g id="node13" class="node">
|
||||
<title>django_contrib_contenttypes_models_ContentType</title>
|
||||
<polygon fill="white" stroke="transparent" points="304.5,-7.5 304.5,-28.5 390.5,-28.5 390.5,-7.5 304.5,-7.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="304.5,-7 304.5,-28 390.5,-28 390.5,-7 304.5,-7"/>
|
||||
<text text-anchor="start" x="308.5" y="-15.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="313.5" y="-15.4" font-family="Roboto" font-size="12.00" fill="white">ContentType</text>
|
||||
<text text-anchor="start" x="381.5" y="-15.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- polymorphic_models_PolymorphicModel->django_contrib_contenttypes_models_ContentType -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>polymorphic_models_PolymorphicModel->django_contrib_contenttypes_models_ContentType</title>
|
||||
<path fill="none" stroke="black" d="M347.5,-72.49C347.5,-60.3 347.5,-46.6 347.5,-36.12"/>
|
||||
<ellipse fill="black" stroke="black" cx="347.5" cy="-76.63" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="463" y="-56.6" font-family="Roboto" font-size="8.00"> polymorphic_ctype (polymorphic_%(app_label)s.%(class)s_set+)</text>
|
||||
</g>
|
||||
<!-- note_models_notes_Note -->
|
||||
<g id="node2" class="node">
|
||||
<title>note_models_notes_Note</title>
|
||||
<polygon fill="white" stroke="transparent" points="366,-183 366,-319 553,-319 553,-183 366,-183"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="367.5,-288 367.5,-318 552.5,-318 552.5,-288 367.5,-288"/>
|
||||
<text text-anchor="start" x="438" y="-306" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="448" y="-306" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Note</text>
|
||||
<text text-anchor="start" x="402.5" y="-296" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white"><</text>
|
||||
<text text-anchor="start" x="409.5" y="-296" font-family="Roboto" font-weight="bold" font-style="italic" font-size="10.00" fill="white">PolymorphicModel</text>
|
||||
<text text-anchor="start" x="498.5" y="-296" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">>    </text>
|
||||
<text text-anchor="start" x="369.5" y="-280.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="379.5" y="-280.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="387.5" y="-280.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="469.5" y="-280.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="479.5" y="-280.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="517.5" y="-280.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="369.5" y="-267.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="379.5" y="-267.6" font-family="Roboto" font-weight="bold" font-style="italic" font-size="8.00">polymorphic_ctype</text>
|
||||
<text text-anchor="start" x="455.5" y="-267.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="469.5" y="-267.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="479.5" y="-267.6" font-family="Roboto" font-weight="bold" font-style="italic" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="540.5" y="-267.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="369.5" y="-254.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="379.5" y="-254.6" font-family="Roboto" font-size="8.00">balance</text>
|
||||
<text text-anchor="start" x="407.5" y="-254.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="469.5" y="-254.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="479.5" y="-254.6" font-family="Roboto" font-size="8.00">BigIntegerField</text>
|
||||
<text text-anchor="start" x="533.5" y="-254.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="369.5" y="-241.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="379.5" y="-241.6" font-family="Roboto" font-size="8.00">created_at</text>
|
||||
<text text-anchor="start" x="417.5" y="-241.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="469.5" y="-241.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="479.5" y="-241.6" font-family="Roboto" font-size="8.00">DateTimeField</text>
|
||||
<text text-anchor="start" x="531.5" y="-241.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="369.5" y="-228.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="379.5" y="-228.6" font-family="Roboto" font-size="8.00">display_image</text>
|
||||
<text text-anchor="start" x="430.5" y="-228.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="469.5" y="-228.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="479.5" y="-228.6" font-family="Roboto" font-size="8.00">ImageField</text>
|
||||
<text text-anchor="start" x="519.5" y="-228.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="369.5" y="-215.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="379.5" y="-215.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">inactivity_reason</text>
|
||||
<text text-anchor="start" x="437.5" y="-215.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="469.5" y="-215.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="479.5" y="-215.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="514.5" y="-215.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="369.5" y="-202.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="379.5" y="-202.6" font-family="Roboto" font-size="8.00">is_active</text>
|
||||
<text text-anchor="start" x="410.5" y="-202.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="469.5" y="-202.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="479.5" y="-202.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="525.5" y="-202.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="369.5" y="-189.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="379.5" y="-189.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">last_negative</text>
|
||||
<text text-anchor="start" x="426.5" y="-189.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="469.5" y="-189.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="479.5" y="-189.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">DateTimeField</text>
|
||||
<text text-anchor="start" x="531.5" y="-189.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="366,-183 366,-319 553,-319 553,-183 366,-183"/>
|
||||
</g>
|
||||
<!-- note_models_notes_Note->polymorphic_models_PolymorphicModel -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>note_models_notes_Note->polymorphic_models_PolymorphicModel</title>
|
||||
<path fill="none" stroke="black" d="M404.81,-178.71C392.51,-162.68 380.13,-146.54 370.04,-133.38"/>
|
||||
<polygon fill="none" stroke="black" points="372.62,-131 363.76,-125.2 367.07,-135.26 372.62,-131"/>
|
||||
<text text-anchor="middle" x="410.5" y="-154.6" font-family="Roboto" font-size="8.00"> abstract</text>
|
||||
<text text-anchor="middle" x="410.5" y="-145.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- note_models_notes_NoteUser -->
|
||||
<g id="node3" class="node">
|
||||
<title>note_models_notes_NoteUser</title>
|
||||
<polygon fill="white" stroke="transparent" points="8,-450.5 8,-499.5 167,-499.5 167,-450.5 8,-450.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="9.5,-477 9.5,-498 166.5,-498 166.5,-477 9.5,-477"/>
|
||||
<text text-anchor="start" x="49" y="-486" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="59" y="-486" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    NoteUser    </text>
|
||||
<text text-anchor="start" x="11.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-469.6" font-family="Roboto" font-weight="bold" font-size="8.00">note_ptr</text>
|
||||
<text text-anchor="start" x="54.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="68.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-469.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="154.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21.5" y="-456.6" font-family="Roboto" font-weight="bold" font-size="8.00">user</text>
|
||||
<text text-anchor="start" x="39.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="68.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="78.5" y="-456.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="154.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="8,-450.5 8,-499.5 167,-499.5 167,-450.5 8,-450.5"/>
|
||||
</g>
|
||||
<!-- note_models_notes_NoteUser->note_models_notes_Note -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>note_models_notes_NoteUser->note_models_notes_Note</title>
|
||||
<path fill="none" stroke="black" d="M109.11,-446.28C127.2,-424.73 154.77,-395.49 184.5,-377 247.87,-337.59 275.92,-354.68 343.5,-323 345.12,-322.24 346.74,-321.47 348.37,-320.68"/>
|
||||
<polygon fill="none" stroke="black" points="350.34,-323.6 357.71,-315.99 347.2,-317.34 350.34,-323.6"/>
|
||||
<text text-anchor="middle" x="311.5" y="-352.6" font-family="Roboto" font-size="8.00"> multi-table</text>
|
||||
<text text-anchor="middle" x="311.5" y="-343.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- django_contrib_auth_models_User -->
|
||||
<g id="node14" class="node">
|
||||
<title>django_contrib_auth_models_User</title>
|
||||
<polygon fill="white" stroke="transparent" points="65.5,-240.5 65.5,-261.5 109.5,-261.5 109.5,-240.5 65.5,-240.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="65.5,-240 65.5,-261 109.5,-261 109.5,-240 65.5,-240"/>
|
||||
<text text-anchor="start" x="69.5" y="-248.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="74.5" y="-248.4" font-family="Roboto" font-size="12.00" fill="white">User</text>
|
||||
<text text-anchor="start" x="100.5" y="-248.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- note_models_notes_NoteUser->django_contrib_auth_models_User -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>note_models_notes_NoteUser->django_contrib_auth_models_User</title>
|
||||
<path fill="none" stroke="black" d="M87.5,-446.33C87.5,-399.84 87.5,-307.38 87.5,-269.15"/>
|
||||
<text text-anchor="middle" x="109" y="-348.1" font-family="Roboto" font-size="8.00"> user (note)</text>
|
||||
</g>
|
||||
<!-- note_models_notes_NoteClub -->
|
||||
<g id="node4" class="node">
|
||||
<title>note_models_notes_NoteClub</title>
|
||||
<polygon fill="white" stroke="transparent" points="1131,-450.5 1131,-499.5 1290,-499.5 1290,-450.5 1131,-450.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="1132.5,-477 1132.5,-498 1289.5,-498 1289.5,-477 1132.5,-477"/>
|
||||
<text text-anchor="start" x="1171.5" y="-486" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1181.5" y="-486" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    NoteClub    </text>
|
||||
<text text-anchor="start" x="1134.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1144.5" y="-469.6" font-family="Roboto" font-weight="bold" font-size="8.00">note_ptr</text>
|
||||
<text text-anchor="start" x="1177.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1191.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1201.5" y="-469.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="1277.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1134.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1144.5" y="-456.6" font-family="Roboto" font-weight="bold" font-size="8.00">club</text>
|
||||
<text text-anchor="start" x="1162.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1191.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1201.5" y="-456.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="1277.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="1131,-450.5 1131,-499.5 1290,-499.5 1290,-450.5 1131,-450.5"/>
|
||||
</g>
|
||||
<!-- note_models_notes_NoteClub->note_models_notes_Note -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>note_models_notes_NoteClub->note_models_notes_Note</title>
|
||||
<path fill="none" stroke="black" d="M1190.62,-446.48C1172.94,-424.15 1145.07,-393.72 1113.5,-377 939.22,-284.69 706.77,-259.9 571.09,-253.63"/>
|
||||
<polygon fill="none" stroke="black" points="571.23,-250.13 561.09,-253.2 570.92,-257.13 571.23,-250.13"/>
|
||||
<text text-anchor="middle" x="1084.5" y="-352.6" font-family="Roboto" font-size="8.00"> multi-table</text>
|
||||
<text text-anchor="middle" x="1084.5" y="-343.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- member_models_Club -->
|
||||
<g id="node15" class="node">
|
||||
<title>member_models_Club</title>
|
||||
<polygon fill="white" stroke="transparent" points="1188.5,-240.5 1188.5,-261.5 1232.5,-261.5 1232.5,-240.5 1188.5,-240.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="1188.5,-240 1188.5,-261 1232.5,-261 1232.5,-240 1188.5,-240"/>
|
||||
<text text-anchor="start" x="1192.5" y="-248.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="1197.5" y="-248.4" font-family="Roboto" font-size="12.00" fill="white">Club</text>
|
||||
<text text-anchor="start" x="1223.5" y="-248.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- note_models_notes_NoteClub->member_models_Club -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>note_models_notes_NoteClub->member_models_Club</title>
|
||||
<path fill="none" stroke="black" d="M1210.5,-446.33C1210.5,-399.84 1210.5,-307.38 1210.5,-269.15"/>
|
||||
<text text-anchor="middle" x="1231" y="-348.1" font-family="Roboto" font-size="8.00"> club (note)</text>
|
||||
</g>
|
||||
<!-- note_models_notes_NoteSpecial -->
|
||||
<g id="node5" class="node">
|
||||
<title>note_models_notes_NoteSpecial</title>
|
||||
<polygon fill="white" stroke="transparent" points="926.5,-450.5 926.5,-499.5 1096.5,-499.5 1096.5,-450.5 926.5,-450.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="927.5,-477 927.5,-498 1095.5,-498 1095.5,-477 927.5,-477"/>
|
||||
<text text-anchor="start" x="966" y="-486" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="976" y="-486" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    NoteSpecial    </text>
|
||||
<text text-anchor="start" x="929.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="939.5" y="-469.6" font-family="Roboto" font-weight="bold" font-size="8.00">note_ptr</text>
|
||||
<text text-anchor="start" x="972.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="997.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1007.5" y="-469.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="1083.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="929.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="939.5" y="-456.6" font-family="Roboto" font-size="8.00">special_type</text>
|
||||
<text text-anchor="start" x="983.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="997.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1007.5" y="-456.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="1042.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="926.5,-450.5 926.5,-499.5 1096.5,-499.5 1096.5,-450.5 926.5,-450.5"/>
|
||||
</g>
|
||||
<!-- note_models_notes_NoteSpecial->note_models_notes_Note -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>note_models_notes_NoteSpecial->note_models_notes_Note</title>
|
||||
<path fill="none" stroke="black" d="M989.16,-446.3C970.14,-424.47 940.97,-394.83 909.5,-377 803.05,-316.67 665.72,-284.03 570.92,-267.4"/>
|
||||
<polygon fill="none" stroke="black" points="571.47,-263.94 561.02,-265.69 570.28,-270.84 571.47,-263.94"/>
|
||||
<text text-anchor="middle" x="884.5" y="-352.6" font-family="Roboto" font-size="8.00"> multi-table</text>
|
||||
<text text-anchor="middle" x="884.5" y="-343.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- note_models_notes_Alias -->
|
||||
<g id="node6" class="node">
|
||||
<title>note_models_notes_Alias</title>
|
||||
<polygon fill="white" stroke="transparent" points="717,-437.5 717,-512.5 892,-512.5 892,-437.5 717,-437.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="718.5,-490 718.5,-511 891.5,-511 891.5,-490 718.5,-490"/>
|
||||
<text text-anchor="start" x="775.5" y="-499" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="785.5" y="-499" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Alias    </text>
|
||||
<text text-anchor="start" x="720.5" y="-482.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="730.5" y="-482.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="738.5" y="-482.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="808.5" y="-482.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="818.5" y="-482.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="856.5" y="-482.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="720.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="730.5" y="-469.6" font-family="Roboto" font-weight="bold" font-size="8.00">note</text>
|
||||
<text text-anchor="start" x="748.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="808.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="818.5" y="-469.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="879.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="720.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="730.5" y="-456.6" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="751.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="808.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="818.5" y="-456.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="853.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="720.5" y="-443.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="730.5" y="-443.6" font-family="Roboto" font-size="8.00">normalized_name</text>
|
||||
<text text-anchor="start" x="794.5" y="-443.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="808.5" y="-443.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="818.5" y="-443.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="853.5" y="-443.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="717,-437.5 717,-512.5 892,-512.5 892,-437.5 717,-437.5"/>
|
||||
</g>
|
||||
<!-- note_models_notes_Alias->note_models_notes_Note -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>note_models_notes_Alias->note_models_notes_Note</title>
|
||||
<path fill="none" stroke="black" d="M759.38,-427.39C741.62,-410.4 720.47,-391.75 699.5,-377 656.46,-346.73 605.15,-319.02 561.03,-297.42"/>
|
||||
<ellipse fill="black" stroke="black" cx="762.4" cy="-430.3" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="691" y="-348.1" font-family="Roboto" font-size="8.00"> note (alias)</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_TemplateCategory -->
|
||||
<g id="node7" class="node">
|
||||
<title>note_models_transactions_TemplateCategory</title>
|
||||
<polygon fill="white" stroke="transparent" points="552,-450.5 552,-499.5 683,-499.5 683,-450.5 552,-450.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="553.5,-477 553.5,-498 682.5,-498 682.5,-477 553.5,-477"/>
|
||||
<text text-anchor="start" x="558.5" y="-486" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.5" y="-486" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    TemplateCategory    </text>
|
||||
<text text-anchor="start" x="555.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="565.5" y="-469.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="573.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="611.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="621.5" y="-469.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="659.5" y="-469.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="555.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="565.5" y="-456.6" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="586.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="611.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="621.5" y="-456.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="656.5" y="-456.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="552,-450.5 552,-499.5 683,-499.5 683,-450.5 552,-450.5"/>
|
||||
</g>
|
||||
<!-- note_models_transactions_TransactionTemplate -->
|
||||
<g id="node8" class="node">
|
||||
<title>note_models_transactions_TransactionTemplate</title>
|
||||
<polygon fill="white" stroke="transparent" points="625,-631 625,-758 806,-758 806,-631 625,-631"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="626.5,-735.5 626.5,-756.5 805.5,-756.5 805.5,-735.5 626.5,-735.5"/>
|
||||
<text text-anchor="start" x="651" y="-744.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="661" y="-744.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    TransactionTemplate    </text>
|
||||
<text text-anchor="start" x="628.5" y="-728.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="638.5" y="-728.1" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="646.5" y="-728.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="698.5" y="-728.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="708.5" y="-728.1" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="746.5" y="-728.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="628.5" y="-715.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="638.5" y="-715.1" font-family="Roboto" font-weight="bold" font-size="8.00">category</text>
|
||||
<text text-anchor="start" x="672.5" y="-715.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="698.5" y="-715.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="708.5" y="-715.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="769.5" y="-715.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="628.5" y="-702.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="638.5" y="-702.1" font-family="Roboto" font-weight="bold" font-size="8.00">destination</text>
|
||||
<text text-anchor="start" x="684.5" y="-702.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="698.5" y="-702.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="708.5" y="-702.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (note_ptr)</text>
|
||||
<text text-anchor="start" x="793.5" y="-702.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="628.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="638.5" y="-689.1" font-family="Roboto" font-size="8.00">amount</text>
|
||||
<text text-anchor="start" x="666.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="698.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="708.5" y="-689.1" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="778.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="628.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="638.5" y="-676.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">description</text>
|
||||
<text text-anchor="start" x="677.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="698.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="708.5" y="-676.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="743.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="628.5" y="-663.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="638.5" y="-663.1" font-family="Roboto" font-size="8.00">display</text>
|
||||
<text text-anchor="start" x="663.5" y="-663.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="698.5" y="-663.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="708.5" y="-663.1" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="754.5" y="-663.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="628.5" y="-650.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="638.5" y="-650.1" font-family="Roboto" font-size="8.00">highlighted</text>
|
||||
<text text-anchor="start" x="677.5" y="-650.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="698.5" y="-650.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="708.5" y="-650.1" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="754.5" y="-650.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="628.5" y="-637.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="638.5" y="-637.1" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="659.5" y="-637.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="698.5" y="-637.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="708.5" y="-637.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="743.5" y="-637.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="625,-631 625,-758 806,-758 806,-631 625,-631"/>
|
||||
</g>
|
||||
<!-- note_models_transactions_TransactionTemplate->note_models_notes_NoteClub -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>note_models_transactions_TransactionTemplate->note_models_notes_NoteClub</title>
|
||||
<path fill="none" stroke="black" d="M822.23,-675.98C905.89,-658.86 1022.9,-627.27 1113.5,-573 1143.74,-554.89 1171.43,-525.3 1189.42,-503.55"/>
|
||||
<ellipse fill="black" stroke="black" cx="818.16" cy="-676.8" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="1100.5" y="-598.1" font-family="Roboto" font-size="8.00"> destination (+)</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_TransactionTemplate->note_models_transactions_TemplateCategory -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>note_models_transactions_TransactionTemplate->note_models_transactions_TemplateCategory</title>
|
||||
<path fill="none" stroke="black" d="M682.12,-619.41C664.15,-579.54 643.06,-532.72 629.98,-503.71"/>
|
||||
<ellipse fill="black" stroke="black" cx="683.79" cy="-623.12" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="713.5" y="-598.1" font-family="Roboto" font-size="8.00"> category (templates)</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_Transaction -->
|
||||
<g id="node9" class="node">
|
||||
<title>note_models_transactions_Transaction</title>
|
||||
<polygon fill="white" stroke="transparent" points="201.5,-381 201.5,-569 397.5,-569 397.5,-381 201.5,-381"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="202.5,-538 202.5,-568 396.5,-568 396.5,-538 202.5,-538"/>
|
||||
<text text-anchor="start" x="261" y="-556" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="271" y="-556" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Transaction</text>
|
||||
<text text-anchor="start" x="242" y="-546" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white"><</text>
|
||||
<text text-anchor="start" x="249" y="-546" font-family="Roboto" font-weight="bold" font-style="italic" font-size="10.00" fill="white">PolymorphicModel</text>
|
||||
<text text-anchor="start" x="338" y="-546" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">>    </text>
|
||||
<text text-anchor="start" x="204.5" y="-530.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-530.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="222.5" y="-530.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-530.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-530.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="352.5" y="-530.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-517.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-517.6" font-family="Roboto" font-weight="bold" font-size="8.00">destination</text>
|
||||
<text text-anchor="start" x="260.5" y="-517.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-517.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-517.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="375.5" y="-517.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-504.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-504.6" font-family="Roboto" font-weight="bold" font-style="italic" font-size="8.00">polymorphic_ctype</text>
|
||||
<text text-anchor="start" x="290.5" y="-504.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-504.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-504.6" font-family="Roboto" font-weight="bold" font-style="italic" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="375.5" y="-504.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-491.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-491.6" font-family="Roboto" font-weight="bold" font-size="8.00">source</text>
|
||||
<text text-anchor="start" x="242.5" y="-491.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-491.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-491.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="375.5" y="-491.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-478.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-478.6" font-family="Roboto" font-size="8.00">amount</text>
|
||||
<text text-anchor="start" x="242.5" y="-478.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-478.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-478.6" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="384.5" y="-478.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-465.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-465.6" font-family="Roboto" font-size="8.00">created_at</text>
|
||||
<text text-anchor="start" x="252.5" y="-465.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-465.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-465.6" font-family="Roboto" font-size="8.00">DateTimeField</text>
|
||||
<text text-anchor="start" x="366.5" y="-465.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-452.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-452.6" font-family="Roboto" font-size="8.00">destination_alias</text>
|
||||
<text text-anchor="start" x="273.5" y="-452.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-452.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-452.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="349.5" y="-452.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-439.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-439.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">invalidity_reason</text>
|
||||
<text text-anchor="start" x="272.5" y="-439.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-439.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-439.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="349.5" y="-439.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-426.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-426.6" font-family="Roboto" font-size="8.00">quantity</text>
|
||||
<text text-anchor="start" x="242.5" y="-426.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-426.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-426.6" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="384.5" y="-426.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-413.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-413.6" font-family="Roboto" font-size="8.00">reason</text>
|
||||
<text text-anchor="start" x="239.5" y="-413.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-413.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-413.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="349.5" y="-413.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-400.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-400.6" font-family="Roboto" font-size="8.00">source_alias</text>
|
||||
<text text-anchor="start" x="259.5" y="-400.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-400.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-400.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="349.5" y="-400.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="204.5" y="-387.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214.5" y="-387.6" font-family="Roboto" font-size="8.00">valid</text>
|
||||
<text text-anchor="start" x="230.5" y="-387.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="304.5" y="-387.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="314.5" y="-387.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="360.5" y="-387.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="201.5,-381 201.5,-569 397.5,-569 397.5,-381 201.5,-381"/>
|
||||
</g>
|
||||
<!-- note_models_transactions_Transaction->polymorphic_models_PolymorphicModel -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>note_models_transactions_Transaction->polymorphic_models_PolymorphicModel</title>
|
||||
<path fill="none" stroke="black" d="M291.68,-376.8C289.64,-318.52 291.7,-243.46 308.5,-179 312.59,-163.31 320.42,-147.19 327.99,-133.93"/>
|
||||
<polygon fill="none" stroke="black" points="331.05,-135.64 333.14,-125.26 325.03,-132.07 331.05,-135.64"/>
|
||||
<text text-anchor="middle" x="328.5" y="-253.6" font-family="Roboto" font-size="8.00"> abstract</text>
|
||||
<text text-anchor="middle" x="328.5" y="-244.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_Transaction->note_models_notes_Note -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>note_models_transactions_Transaction->note_models_notes_Note</title>
|
||||
<path fill="none" stroke="black" d="M407.37,-370.47C410.2,-366.68 412.92,-362.85 415.5,-359 422.87,-347.98 429.37,-335.54 434.95,-323.13"/>
|
||||
<ellipse fill="black" stroke="black" cx="404.93" cy="-373.64" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="445.5" y="-348.1" font-family="Roboto" font-size="8.00"> source (+)</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_Transaction->note_models_notes_Note -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>note_models_transactions_Transaction->note_models_notes_Note</title>
|
||||
<path fill="none" stroke="black" d="M339.68,-369.46C345.09,-359.49 351.03,-349.83 357.5,-341 361.94,-334.93 366.87,-328.99 372.09,-323.24"/>
|
||||
<ellipse fill="black" stroke="black" cx="337.7" cy="-373.21" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="384.5" y="-348.1" font-family="Roboto" font-size="8.00"> destination (+)</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_RecurrentTransaction -->
|
||||
<g id="node10" class="node">
|
||||
<title>note_models_transactions_RecurrentTransaction</title>
|
||||
<polygon fill="white" stroke="transparent" points="39,-811 39,-860 226,-860 226,-811 39,-811"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="40.5,-837.5 40.5,-858.5 225.5,-858.5 225.5,-837.5 40.5,-837.5"/>
|
||||
<text text-anchor="start" x="66" y="-846.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="76" y="-846.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    RecurrentTransaction    </text>
|
||||
<text text-anchor="start" x="42.5" y="-830.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="52.5" y="-830.1" font-family="Roboto" font-weight="bold" font-size="8.00">transaction_ptr</text>
|
||||
<text text-anchor="start" x="113.5" y="-830.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="127.5" y="-830.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="137.5" y="-830.1" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="213.5" y="-830.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="42.5" y="-817.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="52.5" y="-817.1" font-family="Roboto" font-weight="bold" font-size="8.00">template</text>
|
||||
<text text-anchor="start" x="86.5" y="-817.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="127.5" y="-817.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="137.5" y="-817.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="198.5" y="-817.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="39,-811 39,-860 226,-860 226,-811 39,-811"/>
|
||||
</g>
|
||||
<!-- note_models_transactions_RecurrentTransaction->note_models_transactions_TransactionTemplate -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>note_models_transactions_RecurrentTransaction->note_models_transactions_TransactionTemplate</title>
|
||||
<path fill="none" stroke="black" d="M242.27,-830.15C340.58,-823.38 487.34,-806.09 607.5,-762 610.6,-760.86 613.71,-759.63 616.81,-758.32"/>
|
||||
<ellipse fill="black" stroke="black" cx="238.09" cy="-830.43" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="601.5" y="-782.6" font-family="Roboto" font-size="8.00"> template (recurrenttransaction)</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_RecurrentTransaction->note_models_transactions_Transaction -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>note_models_transactions_RecurrentTransaction->note_models_transactions_Transaction</title>
|
||||
<path fill="none" stroke="black" d="M124.08,-806.79C113.13,-765.51 98.42,-686.36 125.5,-627 127.55,-622.52 153.83,-599.09 185.37,-571.97"/>
|
||||
<polygon fill="none" stroke="black" points="187.73,-574.55 193.05,-565.39 183.18,-569.24 187.73,-574.55"/>
|
||||
<text text-anchor="middle" x="145.5" y="-697.1" font-family="Roboto" font-size="8.00"> multi-table</text>
|
||||
<text text-anchor="middle" x="145.5" y="-688.1" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_SpecialTransaction -->
|
||||
<g id="node11" class="node">
|
||||
<title>note_models_transactions_SpecialTransaction</title>
|
||||
<polygon fill="white" stroke="transparent" points="183,-657 183,-732 370,-732 370,-657 183,-657"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="184.5,-709.5 184.5,-730.5 369.5,-730.5 369.5,-709.5 184.5,-709.5"/>
|
||||
<text text-anchor="start" x="215.5" y="-718.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="225.5" y="-718.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    SpecialTransaction    </text>
|
||||
<text text-anchor="start" x="186.5" y="-702.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="196.5" y="-702.1" font-family="Roboto" font-weight="bold" font-size="8.00">transaction_ptr</text>
|
||||
<text text-anchor="start" x="257.5" y="-702.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="271.5" y="-702.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="281.5" y="-702.1" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="357.5" y="-702.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="186.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="196.5" y="-689.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">bank</text>
|
||||
<text text-anchor="start" x="214.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="271.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="281.5" y="-689.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="316.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="186.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="196.5" y="-676.1" font-family="Roboto" font-size="8.00">first_name</text>
|
||||
<text text-anchor="start" x="234.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="271.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="281.5" y="-676.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="316.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="186.5" y="-663.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="196.5" y="-663.1" font-family="Roboto" font-size="8.00">last_name</text>
|
||||
<text text-anchor="start" x="233.5" y="-663.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="271.5" y="-663.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="281.5" y="-663.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="316.5" y="-663.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="183,-657 183,-732 370,-732 370,-657 183,-657"/>
|
||||
</g>
|
||||
<!-- note_models_transactions_SpecialTransaction->note_models_transactions_Transaction -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>note_models_transactions_SpecialTransaction->note_models_transactions_Transaction</title>
|
||||
<path fill="none" stroke="black" d="M280.8,-652.85C282.89,-633.07 285.51,-608.27 288.13,-583.55"/>
|
||||
<polygon fill="none" stroke="black" points="291.63,-583.66 289.21,-573.35 284.67,-582.92 291.63,-583.66"/>
|
||||
<text text-anchor="middle" x="307.5" y="-602.6" font-family="Roboto" font-size="8.00"> multi-table</text>
|
||||
<text text-anchor="middle" x="307.5" y="-593.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_MembershipTransaction -->
|
||||
<g id="node12" class="node">
|
||||
<title>note_models_transactions_MembershipTransaction</title>
|
||||
<polygon fill="white" stroke="transparent" points="404,-670 404,-719 591,-719 591,-670 404,-670"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="405.5,-696.5 405.5,-717.5 590.5,-717.5 590.5,-696.5 405.5,-696.5"/>
|
||||
<text text-anchor="start" x="425" y="-705.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="435" y="-705.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    MembershipTransaction    </text>
|
||||
<text text-anchor="start" x="407.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="417.5" y="-689.1" font-family="Roboto" font-weight="bold" font-size="8.00">transaction_ptr</text>
|
||||
<text text-anchor="start" x="478.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="492.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="502.5" y="-689.1" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="578.5" y="-689.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="407.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="417.5" y="-676.1" font-family="Roboto" font-weight="bold" font-size="8.00">membership</text>
|
||||
<text text-anchor="start" x="466.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="492.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="502.5" y="-676.1" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="578.5" y="-676.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="404,-670 404,-719 591,-719 591,-670 404,-670"/>
|
||||
</g>
|
||||
<!-- note_models_transactions_MembershipTransaction->note_models_transactions_Transaction -->
|
||||
<g id="edge18" class="edge">
|
||||
<title>note_models_transactions_MembershipTransaction->note_models_transactions_Transaction</title>
|
||||
<path fill="none" stroke="black" d="M472.42,-665.95C452.58,-644.15 423.52,-612.24 394.88,-580.77"/>
|
||||
<polygon fill="none" stroke="black" points="397.32,-578.25 388,-573.21 392.14,-582.97 397.32,-578.25"/>
|
||||
<text text-anchor="middle" x="436.5" y="-602.6" font-family="Roboto" font-size="8.00"> multi-table</text>
|
||||
<text text-anchor="middle" x="436.5" y="-593.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- member_models_Membership -->
|
||||
<g id="node16" class="node">
|
||||
<title>member_models_Membership</title>
|
||||
<polygon fill="white" stroke="transparent" points="431.5,-464.5 431.5,-485.5 517.5,-485.5 517.5,-464.5 431.5,-464.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="431.5,-464 431.5,-485 517.5,-485 517.5,-464 431.5,-464"/>
|
||||
<text text-anchor="start" x="436" y="-472.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="441" y="-472.4" font-family="Roboto" font-size="12.00" fill="white">Membership</text>
|
||||
<text text-anchor="start" x="508" y="-472.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- note_models_transactions_MembershipTransaction->member_models_Membership -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>note_models_transactions_MembershipTransaction->member_models_Membership</title>
|
||||
<path fill="none" stroke="black" d="M494.59,-665.95C489.76,-620.34 480.26,-530.46 476.3,-493.01"/>
|
||||
<text text-anchor="middle" x="535.5" y="-598.1" font-family="Roboto" font-size="8.00"> membership (transaction)</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 55 KiB |
171
docs/_static/img/graphs/permission.svg
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.44.1 (0)
|
||||
-->
|
||||
<!-- Title: model_graph Pages: 1 -->
|
||||
<svg width="352pt" height="373pt"
|
||||
viewBox="0.00 0.00 352.00 373.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 369)">
|
||||
<title>model_graph</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-369 348,-369 348,4 -4,4"/>
|
||||
<!-- permission_models_PermissionMask -->
|
||||
<g id="node1" class="node">
|
||||
<title>permission_models_PermissionMask</title>
|
||||
<polygon fill="white" stroke="transparent" points="8,-4 8,-66 186,-66 186,-4 8,-4"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="9,-44 9,-65 185,-65 185,-44 9,-44"/>
|
||||
<text text-anchor="start" x="41" y="-53" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="51" y="-53" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    PermissionMask    </text>
|
||||
<text text-anchor="start" x="11" y="-36.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-36.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="29" y="-36.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-36.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-36.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="122" y="-36.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-23.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-23.6" font-family="Roboto" font-size="8.00">description</text>
|
||||
<text text-anchor="start" x="60" y="-23.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-23.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-23.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="119" y="-23.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-10.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-10.6" font-family="Roboto" font-size="8.00">rank</text>
|
||||
<text text-anchor="start" x="37" y="-10.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-10.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-10.6" font-family="Roboto" font-size="8.00">PositiveSmallIntegerField</text>
|
||||
<text text-anchor="start" x="173" y="-10.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="8,-4 8,-66 186,-66 186,-4 8,-4"/>
|
||||
</g>
|
||||
<!-- permission_models_Permission -->
|
||||
<g id="node2" class="node">
|
||||
<title>permission_models_Permission</title>
|
||||
<polygon fill="white" stroke="transparent" points="104.5,-119 104.5,-246 255.5,-246 255.5,-119 104.5,-119"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="106,-223.5 106,-244.5 255,-244.5 255,-223.5 106,-223.5"/>
|
||||
<text text-anchor="start" x="136.5" y="-232.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="146.5" y="-232.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Permission    </text>
|
||||
<text text-anchor="start" x="108" y="-216.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="118" y="-216.1" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="126" y="-216.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="172" y="-216.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="182" y="-216.1" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="220" y="-216.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="108" y="-203.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="118" y="-203.1" font-family="Roboto" font-weight="bold" font-size="8.00">mask</text>
|
||||
<text text-anchor="start" x="139" y="-203.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="172" y="-203.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="182" y="-203.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="243" y="-203.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="108" y="-190.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="118" y="-190.1" font-family="Roboto" font-weight="bold" font-size="8.00">model</text>
|
||||
<text text-anchor="start" x="143" y="-190.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="172" y="-190.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="182" y="-190.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="243" y="-190.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="108" y="-177.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="118" y="-177.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">description</text>
|
||||
<text text-anchor="start" x="157" y="-177.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="172" y="-177.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="182" y="-177.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="217" y="-177.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="108" y="-164.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="118" y="-164.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">field</text>
|
||||
<text text-anchor="start" x="133" y="-164.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="172" y="-164.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="182" y="-164.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="217" y="-164.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="108" y="-151.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="118" y="-151.1" font-family="Roboto" font-size="8.00">permanent</text>
|
||||
<text text-anchor="start" x="158" y="-151.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="172" y="-151.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="182" y="-151.1" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="228" y="-151.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="108" y="-138.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="118" y="-138.1" font-family="Roboto" font-size="8.00">query</text>
|
||||
<text text-anchor="start" x="139" y="-138.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="172" y="-138.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="182" y="-138.1" font-family="Roboto" font-size="8.00">TextField</text>
|
||||
<text text-anchor="start" x="214" y="-138.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="108" y="-125.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="118" y="-125.1" font-family="Roboto" font-size="8.00">type</text>
|
||||
<text text-anchor="start" x="134" y="-125.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="172" y="-125.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="182" y="-125.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="217" y="-125.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="104.5,-119 104.5,-246 255.5,-246 255.5,-119 104.5,-119"/>
|
||||
</g>
|
||||
<!-- permission_models_Permission->permission_models_PermissionMask -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>permission_models_Permission->permission_models_PermissionMask</title>
|
||||
<path fill="none" stroke="black" d="M137.78,-107.48C130.35,-94.46 122.95,-81.49 116.54,-70.25"/>
|
||||
<ellipse fill="black" stroke="black" cx="139.92" cy="-111.24" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="168" y="-90.6" font-family="Roboto" font-size="8.00"> mask (permissions)</text>
|
||||
</g>
|
||||
<!-- django_contrib_contenttypes_models_ContentType -->
|
||||
<g id="node4" class="node">
|
||||
<title>django_contrib_contenttypes_models_ContentType</title>
|
||||
<polygon fill="white" stroke="transparent" points="220,-24.5 220,-45.5 306,-45.5 306,-24.5 220,-24.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="220,-24 220,-45 306,-45 306,-24 220,-24"/>
|
||||
<text text-anchor="start" x="224" y="-32.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="229" y="-32.4" font-family="Roboto" font-size="12.00" fill="white">ContentType</text>
|
||||
<text text-anchor="start" x="297" y="-32.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- permission_models_Permission->django_contrib_contenttypes_models_ContentType -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>permission_models_Permission->django_contrib_contenttypes_models_ContentType</title>
|
||||
<path fill="none" stroke="black" d="M222.21,-107.51C233.83,-87.14 245.38,-66.89 253.2,-53.17"/>
|
||||
<ellipse fill="black" stroke="black" cx="220.08" cy="-111.24" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="249.5" y="-90.6" font-family="Roboto" font-size="8.00"> model (+)</text>
|
||||
</g>
|
||||
<!-- permission_models_Role -->
|
||||
<g id="node3" class="node">
|
||||
<title>permission_models_Role</title>
|
||||
<polygon fill="white" stroke="transparent" points="173,-299 173,-361 317,-361 317,-299 173,-299"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="174,-339 174,-360 316,-360 316,-339 174,-339"/>
|
||||
<text text-anchor="start" x="217" y="-348" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="227" y="-348" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Role    </text>
|
||||
<text text-anchor="start" x="176" y="-331.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="186" y="-331.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="194" y="-331.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="233" y="-331.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="243" y="-331.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="281" y="-331.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="176" y="-318.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="186" y="-318.6" font-family="Roboto" font-weight="bold" font-size="8.00">for_club</text>
|
||||
<text text-anchor="start" x="219" y="-318.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="233" y="-318.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="243" y="-318.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="304" y="-318.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="176" y="-305.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="186" y="-305.6" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="207" y="-305.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="233" y="-305.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="243" y="-305.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="278" y="-305.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="173,-299 173,-361 317,-361 317,-299 173,-299"/>
|
||||
</g>
|
||||
<!-- permission_models_Role->permission_models_Permission -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>permission_models_Role->permission_models_Permission</title>
|
||||
<path fill="none" stroke="black" d="M226.45,-287.47C222.27,-278.13 217.73,-267.95 213.16,-257.73"/>
|
||||
<ellipse fill="black" stroke="black" cx="228.13" cy="-291.23" rx="4" ry="4"/>
|
||||
<ellipse fill="black" stroke="black" cx="211.47" cy="-253.94" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="254" y="-270.6" font-family="Roboto" font-size="8.00"> permissions (role)</text>
|
||||
</g>
|
||||
<!-- member_models_Club -->
|
||||
<g id="node5" class="node">
|
||||
<title>member_models_Club</title>
|
||||
<polygon fill="white" stroke="transparent" points="289,-172 289,-193 333,-193 333,-172 289,-172"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="289,-171.5 289,-192.5 333,-192.5 333,-171.5 289,-171.5"/>
|
||||
<text text-anchor="start" x="293" y="-179.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="298" y="-179.9" font-family="Roboto" font-size="12.00" fill="white">Club</text>
|
||||
<text text-anchor="start" x="324" y="-179.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- permission_models_Role->member_models_Club -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>permission_models_Role->member_models_Club</title>
|
||||
<path fill="none" stroke="black" d="M282.52,-288.02C284.92,-284.43 287.12,-280.73 289,-277 301.53,-252.13 307.07,-219.92 309.41,-200.53"/>
|
||||
<ellipse fill="black" stroke="black" cx="280.03" cy="-291.53" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="318" y="-270.6" font-family="Roboto" font-size="8.00"> for_club (role)</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 15 KiB |
324
docs/_static/img/graphs/treasury.svg
vendored
Normal file
@ -0,0 +1,324 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.44.1 (0)
|
||||
-->
|
||||
<!-- Title: model_graph Pages: 1 -->
|
||||
<svg width="970pt" height="493pt"
|
||||
viewBox="0.00 0.00 970.00 493.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 489)">
|
||||
<title>model_graph</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-489 966,-489 966,4 -4,4"/>
|
||||
<!-- treasury_models_Invoice -->
|
||||
<g id="node1" class="node">
|
||||
<title>treasury_models_Invoice</title>
|
||||
<polygon fill="white" stroke="transparent" points="8,-187 8,-340 176,-340 176,-187 8,-187"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="9,-317.5 9,-338.5 175,-338.5 175,-317.5 9,-317.5"/>
|
||||
<text text-anchor="start" x="57.5" y="-326.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="67.5" y="-326.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Invoice    </text>
|
||||
<text text-anchor="start" x="11" y="-310.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-310.1" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="29" y="-310.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-310.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-310.1" font-family="Roboto" font-weight="bold" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="163" y="-310.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-297.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-297.1" font-family="Roboto" font-size="8.00">acquitted</text>
|
||||
<text text-anchor="start" x="54" y="-297.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-297.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-297.1" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="130" y="-297.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-284.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-284.1" font-family="Roboto" font-size="8.00">address</text>
|
||||
<text text-anchor="start" x="49" y="-284.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-284.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-284.1" font-family="Roboto" font-size="8.00">TextField</text>
|
||||
<text text-anchor="start" x="116" y="-284.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-271.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-271.1" font-family="Roboto" font-size="8.00">bde</text>
|
||||
<text text-anchor="start" x="35" y="-271.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-271.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-271.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="119" y="-271.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-258.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-258.1" font-family="Roboto" font-size="8.00">date</text>
|
||||
<text text-anchor="start" x="37" y="-258.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-258.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-258.1" font-family="Roboto" font-size="8.00">DateField</text>
|
||||
<text text-anchor="start" x="118" y="-258.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-245.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-245.1" font-family="Roboto" font-size="8.00">description</text>
|
||||
<text text-anchor="start" x="60" y="-245.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-245.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-245.1" font-family="Roboto" font-size="8.00">TextField</text>
|
||||
<text text-anchor="start" x="116" y="-245.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-232.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-232.1" font-family="Roboto" font-size="8.00">locked</text>
|
||||
<text text-anchor="start" x="44" y="-232.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-232.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-232.1" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="130" y="-232.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-219.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-219.1" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="42" y="-219.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-219.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-219.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="119" y="-219.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-206.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-206.1" font-family="Roboto" font-size="8.00">object</text>
|
||||
<text text-anchor="start" x="43" y="-206.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-206.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-206.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="119" y="-206.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="11" y="-193.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="21" y="-193.1" font-family="Roboto" font-size="8.00">tex</text>
|
||||
<text text-anchor="start" x="32" y="-193.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="74" y="-193.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="84" y="-193.1" font-family="Roboto" font-size="8.00">TextField</text>
|
||||
<text text-anchor="start" x="116" y="-193.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="8,-187 8,-340 176,-340 176,-187 8,-187"/>
|
||||
</g>
|
||||
<!-- treasury_models_Product -->
|
||||
<g id="node2" class="node">
|
||||
<title>treasury_models_Product</title>
|
||||
<polygon fill="white" stroke="transparent" points="11.5,-393 11.5,-481 172.5,-481 172.5,-393 11.5,-393"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="13,-459 13,-480 172,-480 172,-459 13,-459"/>
|
||||
<text text-anchor="start" x="57" y="-468" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="67" y="-468" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Product    </text>
|
||||
<text text-anchor="start" x="15" y="-451.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="25" y="-451.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="33" y="-451.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="80" y="-451.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="90" y="-451.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="128" y="-451.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="15" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="25" y="-438.6" font-family="Roboto" font-weight="bold" font-size="8.00">invoice</text>
|
||||
<text text-anchor="start" x="53" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="80" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="90" y="-438.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="151" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="15" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="25" y="-425.6" font-family="Roboto" font-size="8.00">amount</text>
|
||||
<text text-anchor="start" x="53" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="80" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="90" y="-425.6" font-family="Roboto" font-size="8.00">IntegerField</text>
|
||||
<text text-anchor="start" x="133" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="15" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="25" y="-412.6" font-family="Roboto" font-size="8.00">designation</text>
|
||||
<text text-anchor="start" x="66" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="80" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="90" y="-412.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="125" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="15" y="-399.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="25" y="-399.6" font-family="Roboto" font-size="8.00">quantity</text>
|
||||
<text text-anchor="start" x="53" y="-399.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="80" y="-399.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="90" y="-399.6" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="160" y="-399.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="11.5,-393 11.5,-481 172.5,-481 172.5,-393 11.5,-393"/>
|
||||
</g>
|
||||
<!-- treasury_models_Product->treasury_models_Invoice -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>treasury_models_Product->treasury_models_Invoice</title>
|
||||
<path fill="none" stroke="black" d="M92,-380.43C92,-368.88 92,-356.48 92,-344.25"/>
|
||||
<ellipse fill="black" stroke="black" cx="92" cy="-384.68" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="124.5" y="-364.6" font-family="Roboto" font-size="8.00"> invoice (products)</text>
|
||||
</g>
|
||||
<!-- treasury_models_RemittanceType -->
|
||||
<g id="node3" class="node">
|
||||
<title>treasury_models_RemittanceType</title>
|
||||
<polygon fill="white" stroke="transparent" points="213.5,-85 213.5,-134 382.5,-134 382.5,-85 213.5,-85"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="215,-111.5 215,-132.5 382,-132.5 382,-111.5 215,-111.5"/>
|
||||
<text text-anchor="start" x="243" y="-120.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="253" y="-120.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    RemittanceType    </text>
|
||||
<text text-anchor="start" x="217" y="-104.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="227" y="-104.1" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="235" y="-104.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="259" y="-104.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="269" y="-104.1" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="307" y="-104.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="217" y="-91.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="227" y="-91.1" font-family="Roboto" font-weight="bold" font-size="8.00">note</text>
|
||||
<text text-anchor="start" x="245" y="-91.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="259" y="-91.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="269" y="-91.1" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (note_ptr)</text>
|
||||
<text text-anchor="start" x="370" y="-91.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="213.5,-85 213.5,-134 382.5,-134 382.5,-85 213.5,-85"/>
|
||||
</g>
|
||||
<!-- note_models_notes_NoteSpecial -->
|
||||
<g id="node7" class="node">
|
||||
<title>note_models_notes_NoteSpecial</title>
|
||||
<polygon fill="white" stroke="transparent" points="255,-7.5 255,-28.5 341,-28.5 341,-7.5 255,-7.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="255,-7 255,-28 341,-28 341,-7 255,-7"/>
|
||||
<text text-anchor="start" x="259.5" y="-15.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="264.5" y="-15.4" font-family="Roboto" font-size="12.00" fill="white">NoteSpecial</text>
|
||||
<text text-anchor="start" x="331.5" y="-15.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- treasury_models_RemittanceType->note_models_notes_NoteSpecial -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>treasury_models_RemittanceType->note_models_notes_NoteSpecial</title>
|
||||
<path fill="none" stroke="black" d="M298,-80.67C298,-66.1 298,-48.72 298,-36.13"/>
|
||||
<text text-anchor="middle" x="337.5" y="-56.6" font-family="Roboto" font-size="8.00"> note (remittancetype)</text>
|
||||
</g>
|
||||
<!-- treasury_models_Remittance -->
|
||||
<g id="node4" class="node">
|
||||
<title>treasury_models_Remittance</title>
|
||||
<polygon fill="white" stroke="transparent" points="210.5,-219.5 210.5,-307.5 385.5,-307.5 385.5,-219.5 210.5,-219.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="212,-285.5 212,-306.5 385,-306.5 385,-285.5 212,-285.5"/>
|
||||
<text text-anchor="start" x="254" y="-294.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="264" y="-294.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Remittance    </text>
|
||||
<text text-anchor="start" x="214" y="-278.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="224" y="-278.1" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="232" y="-278.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="302" y="-278.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="312" y="-278.1" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="350" y="-278.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214" y="-265.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="224" y="-265.1" font-family="Roboto" font-weight="bold" font-size="8.00">remittance_type</text>
|
||||
<text text-anchor="start" x="288" y="-265.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="302" y="-265.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="312" y="-265.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="373" y="-265.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214" y="-252.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="224" y="-252.1" font-family="Roboto" font-size="8.00">closed</text>
|
||||
<text text-anchor="start" x="247" y="-252.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="302" y="-252.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="312" y="-252.1" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="358" y="-252.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214" y="-239.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="224" y="-239.1" font-family="Roboto" font-size="8.00">comment</text>
|
||||
<text text-anchor="start" x="258" y="-239.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="302" y="-239.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="312" y="-239.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="347" y="-239.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="214" y="-226.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="224" y="-226.1" font-family="Roboto" font-size="8.00">date</text>
|
||||
<text text-anchor="start" x="240" y="-226.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="302" y="-226.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="312" y="-226.1" font-family="Roboto" font-size="8.00">DateTimeField</text>
|
||||
<text text-anchor="start" x="364" y="-226.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="210.5,-219.5 210.5,-307.5 385.5,-307.5 385.5,-219.5 210.5,-219.5"/>
|
||||
</g>
|
||||
<!-- treasury_models_Remittance->treasury_models_RemittanceType -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>treasury_models_Remittance->treasury_models_RemittanceType</title>
|
||||
<path fill="none" stroke="black" d="M298,-207.09C298,-183.68 298,-157.46 298,-138.24"/>
|
||||
<ellipse fill="black" stroke="black" cx="298" cy="-211.24" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="350" y="-158.6" font-family="Roboto" font-size="8.00"> remittance_type (remittance)</text>
|
||||
</g>
|
||||
<!-- treasury_models_SpecialTransactionProxy -->
|
||||
<g id="node5" class="node">
|
||||
<title>treasury_models_SpecialTransactionProxy</title>
|
||||
<polygon fill="white" stroke="transparent" points="272.5,-406 272.5,-468 497.5,-468 497.5,-406 272.5,-406"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="274,-446 274,-467 497,-467 497,-446 274,-446"/>
|
||||
<text text-anchor="start" x="310" y="-455" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="320" y="-455" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    SpecialTransactionProxy    </text>
|
||||
<text text-anchor="start" x="276" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="286" y="-438.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="294" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="346" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="356" y="-438.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="394" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="276" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="286" y="-425.6" font-family="Roboto" font-weight="bold" font-size="8.00">remittance</text>
|
||||
<text text-anchor="start" x="328" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="346" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="356" y="-425.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="417" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="276" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="286" y="-412.6" font-family="Roboto" font-weight="bold" font-size="8.00">transaction</text>
|
||||
<text text-anchor="start" x="332" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="346" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="356" y="-412.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (transaction_ptr)</text>
|
||||
<text text-anchor="start" x="485" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="272.5,-406 272.5,-468 497.5,-468 497.5,-406 272.5,-406"/>
|
||||
</g>
|
||||
<!-- treasury_models_SpecialTransactionProxy->treasury_models_Remittance -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>treasury_models_SpecialTransactionProxy->treasury_models_Remittance</title>
|
||||
<path fill="none" stroke="black" d="M318.41,-396.34C310.62,-388.91 303.76,-380.46 299,-371 289.94,-353 288.41,-331.02 289.64,-311.71"/>
|
||||
<ellipse fill="black" stroke="black" cx="321.56" cy="-399.15" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="364" y="-364.6" font-family="Roboto" font-size="8.00"> remittance (specialtransactionproxy)</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_SpecialTransaction -->
|
||||
<g id="node8" class="node">
|
||||
<title>note_models_transactions_SpecialTransaction</title>
|
||||
<polygon fill="white" stroke="transparent" points="483,-253 483,-274 605,-274 605,-253 483,-253"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="483,-252.5 483,-273.5 605,-273.5 605,-252.5 483,-252.5"/>
|
||||
<text text-anchor="start" x="487.5" y="-260.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="492.5" y="-260.9" font-family="Roboto" font-size="12.00" fill="white">SpecialTransaction</text>
|
||||
<text text-anchor="start" x="595.5" y="-260.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- treasury_models_SpecialTransactionProxy->note_models_transactions_SpecialTransaction -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>treasury_models_SpecialTransactionProxy->note_models_transactions_SpecialTransaction</title>
|
||||
<path fill="none" stroke="black" d="M415.54,-401.97C426.93,-389.37 440,-374.98 452,-362 478.22,-333.63 509.12,-301.06 527.61,-281.66"/>
|
||||
<text text-anchor="middle" x="518" y="-364.6" font-family="Roboto" font-size="8.00"> transaction (specialtransactionproxy)</text>
|
||||
</g>
|
||||
<!-- treasury_models_SogeCredit -->
|
||||
<g id="node6" class="node">
|
||||
<title>treasury_models_SogeCredit</title>
|
||||
<polygon fill="white" stroke="transparent" points="612,-406 612,-468 864,-468 864,-406 612,-406"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="613,-446 613,-467 863,-467 863,-446 613,-446"/>
|
||||
<text text-anchor="start" x="694.5" y="-455" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="704.5" y="-455" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    SogeCredit    </text>
|
||||
<text text-anchor="start" x="615" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="625" y="-438.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="633" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="712" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="722" y="-438.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="760" y="-438.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="615" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="625" y="-425.6" font-family="Roboto" font-weight="bold" font-size="8.00">credit_transaction</text>
|
||||
<text text-anchor="start" x="698" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="712" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="722" y="-425.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (transaction_ptr)</text>
|
||||
<text text-anchor="start" x="851" y="-425.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="615" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="625" y="-412.6" font-family="Roboto" font-weight="bold" font-size="8.00">user</text>
|
||||
<text text-anchor="start" x="643" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="712" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="722" y="-412.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="798" y="-412.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="612,-406 612,-468 864,-468 864,-406 612,-406"/>
|
||||
</g>
|
||||
<!-- treasury_models_SogeCredit->note_models_transactions_SpecialTransaction -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>treasury_models_SogeCredit->note_models_transactions_SpecialTransaction</title>
|
||||
<path fill="none" stroke="black" d="M668.71,-401.98C653.52,-392.97 638.09,-382.51 625,-371 594.47,-344.16 567.83,-304.24 554.03,-281.68"/>
|
||||
<text text-anchor="middle" x="679.5" y="-364.6" font-family="Roboto" font-size="8.00"> credit_transaction (sogecredit)</text>
|
||||
</g>
|
||||
<!-- django_contrib_auth_models_User -->
|
||||
<g id="node9" class="node">
|
||||
<title>django_contrib_auth_models_User</title>
|
||||
<polygon fill="white" stroke="transparent" points="716,-253 716,-274 760,-274 760,-253 716,-253"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="716,-252.5 716,-273.5 760,-273.5 760,-252.5 716,-252.5"/>
|
||||
<text text-anchor="start" x="720" y="-260.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="725" y="-260.9" font-family="Roboto" font-size="12.00" fill="white">User</text>
|
||||
<text text-anchor="start" x="751" y="-260.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- treasury_models_SogeCredit->django_contrib_auth_models_User -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>treasury_models_SogeCredit->django_contrib_auth_models_User</title>
|
||||
<path fill="none" stroke="black" d="M738,-401.71C738,-365.34 738,-309.39 738,-281.51"/>
|
||||
<text text-anchor="middle" x="769.5" y="-364.6" font-family="Roboto" font-size="8.00"> user (sogecredit)</text>
|
||||
</g>
|
||||
<!-- note_models_transactions_MembershipTransaction -->
|
||||
<g id="node10" class="node">
|
||||
<title>note_models_transactions_MembershipTransaction</title>
|
||||
<polygon fill="white" stroke="transparent" points="794,-253 794,-274 940,-274 940,-253 794,-253"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="794,-252.5 794,-273.5 940,-273.5 940,-252.5 794,-252.5"/>
|
||||
<text text-anchor="start" x="798" y="-260.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="803" y="-260.9" font-family="Roboto" font-size="12.00" fill="white">MembershipTransaction</text>
|
||||
<text text-anchor="start" x="931" y="-260.9" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- treasury_models_SogeCredit->note_models_transactions_MembershipTransaction -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>treasury_models_SogeCredit->note_models_transactions_MembershipTransaction</title>
|
||||
<path fill="none" stroke="black" d="M782.72,-395.99C790.55,-388.06 798.32,-379.54 805,-371 825.4,-344.92 843.79,-311.46 855.15,-289.06"/>
|
||||
<ellipse fill="black" stroke="black" cx="779.57" cy="-399.11" rx="4" ry="4"/>
|
||||
<ellipse fill="black" stroke="black" cx="857.08" cy="-285.21" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="886.5" y="-364.6" font-family="Roboto" font-size="8.00"> transactions (_sogecredit_transactions_+)</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 29 KiB |
603
docs/_static/img/graphs/wei.svg
vendored
Normal file
@ -0,0 +1,603 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.44.1 (0)
|
||||
-->
|
||||
<!-- Title: model_graph Pages: 1 -->
|
||||
<svg width="1207pt" height="864pt"
|
||||
viewBox="0.00 0.00 1206.76 864.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 860)">
|
||||
<title>model_graph</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-860 1202.76,-860 1202.76,4 -4,4"/>
|
||||
<!-- wei_models_WEIClub -->
|
||||
<g id="node1" class="node">
|
||||
<title>wei_models_WEIClub</title>
|
||||
<polygon fill="white" stroke="transparent" points="357.76,-219 357.76,-294 520.76,-294 520.76,-219 357.76,-219"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="359.26,-271.5 359.26,-292.5 520.26,-292.5 520.26,-271.5 359.26,-271.5"/>
|
||||
<text text-anchor="start" x="401.76" y="-280.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="411.76" y="-280.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    WEIClub    </text>
|
||||
<text text-anchor="start" x="361.26" y="-264.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="371.26" y="-264.1" font-family="Roboto" font-weight="bold" font-size="8.00">club_ptr</text>
|
||||
<text text-anchor="start" x="404.26" y="-264.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="422.26" y="-264.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="432.26" y="-264.1" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="508.26" y="-264.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="361.26" y="-251.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="371.26" y="-251.1" font-family="Roboto" font-size="8.00">date_end</text>
|
||||
<text text-anchor="start" x="405.26" y="-251.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="422.26" y="-251.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="432.26" y="-251.1" font-family="Roboto" font-size="8.00">DateField</text>
|
||||
<text text-anchor="start" x="466.26" y="-251.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="361.26" y="-238.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="371.26" y="-238.1" font-family="Roboto" font-size="8.00">date_start</text>
|
||||
<text text-anchor="start" x="408.26" y="-238.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="422.26" y="-238.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="432.26" y="-238.1" font-family="Roboto" font-size="8.00">DateField</text>
|
||||
<text text-anchor="start" x="466.26" y="-238.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="361.26" y="-225.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="371.26" y="-225.1" font-family="Roboto" font-size="8.00">year</text>
|
||||
<text text-anchor="start" x="387.26" y="-225.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="422.26" y="-225.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="432.26" y="-225.1" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="502.26" y="-225.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="357.76,-219 357.76,-294 520.76,-294 520.76,-219 357.76,-219"/>
|
||||
</g>
|
||||
<!-- member_models_Club -->
|
||||
<g id="node8" class="node">
|
||||
<title>member_models_Club</title>
|
||||
<polygon fill="white" stroke="transparent" points="554.76,-4 554.76,-157 763.76,-157 763.76,-4 554.76,-4"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="556.26,-134.5 556.26,-155.5 763.26,-155.5 763.26,-134.5 556.26,-134.5"/>
|
||||
<text text-anchor="start" x="631.26" y="-143.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="641.26" y="-143.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Club    </text>
|
||||
<text text-anchor="start" x="558.26" y="-127.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.26" y="-127.1" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="576.26" y="-127.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="671.26" y="-127.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="681.26" y="-127.1" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="719.26" y="-127.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="558.26" y="-114.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.26" y="-114.1" font-family="Roboto" font-weight="bold" font-size="8.00" fill="#7b7b7b">parent_club</text>
|
||||
<text text-anchor="start" x="616.26" y="-114.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="671.26" y="-114.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="681.26" y="-114.1" font-family="Roboto" font-weight="bold" font-size="8.00" fill="#7b7b7b">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="742.26" y="-114.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="558.26" y="-101.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.26" y="-101.1" font-family="Roboto" font-size="8.00">email</text>
|
||||
<text text-anchor="start" x="587.26" y="-101.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="671.26" y="-101.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="681.26" y="-101.1" font-family="Roboto" font-size="8.00">EmailField</text>
|
||||
<text text-anchor="start" x="718.26" y="-101.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="558.26" y="-88.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.26" y="-88.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">membership_duration</text>
|
||||
<text text-anchor="start" x="647.26" y="-88.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="671.26" y="-88.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="681.26" y="-88.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="751.26" y="-88.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="558.26" y="-75.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.26" y="-75.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">membership_end</text>
|
||||
<text text-anchor="start" x="631.26" y="-75.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="671.26" y="-75.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="681.26" y="-75.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">DateField</text>
|
||||
<text text-anchor="start" x="715.26" y="-75.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="558.26" y="-62.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.26" y="-62.1" font-family="Roboto" font-size="8.00">membership_fee_paid</text>
|
||||
<text text-anchor="start" x="648.26" y="-62.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="671.26" y="-62.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="681.26" y="-62.1" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="751.26" y="-62.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="558.26" y="-49.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.26" y="-49.1" font-family="Roboto" font-size="8.00">membership_fee_unpaid</text>
|
||||
<text text-anchor="start" x="657.26" y="-49.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="671.26" y="-49.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="681.26" y="-49.1" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="751.26" y="-49.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="558.26" y="-36.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.26" y="-36.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">membership_start</text>
|
||||
<text text-anchor="start" x="633.26" y="-36.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="671.26" y="-36.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="681.26" y="-36.1" font-family="Roboto" font-size="8.00" fill="#7b7b7b">DateField</text>
|
||||
<text text-anchor="start" x="715.26" y="-36.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="558.26" y="-23.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.26" y="-23.1" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="589.26" y="-23.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="671.26" y="-23.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="681.26" y="-23.1" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="716.26" y="-23.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="558.26" y="-10.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="568.26" y="-10.1" font-family="Roboto" font-size="8.00">require_memberships</text>
|
||||
<text text-anchor="start" x="645.26" y="-10.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="671.26" y="-10.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="681.26" y="-10.1" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="727.26" y="-10.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="554.76,-4 554.76,-157 763.76,-157 763.76,-4 554.76,-4"/>
|
||||
</g>
|
||||
<!-- wei_models_WEIClub->member_models_Club -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>wei_models_WEIClub->member_models_Club</title>
|
||||
<path fill="none" stroke="black" d="M490.56,-214.92C508.57,-200.68 529.56,-184.08 550.48,-167.53"/>
|
||||
<polygon fill="none" stroke="black" points="552.82,-170.14 558.49,-161.2 548.48,-164.65 552.82,-170.14"/>
|
||||
<text text-anchor="middle" x="550.26" y="-190.6" font-family="Roboto" font-size="8.00"> multi-table</text>
|
||||
<text text-anchor="middle" x="550.26" y="-181.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- wei_models_Bus -->
|
||||
<g id="node2" class="node">
|
||||
<title>wei_models_Bus</title>
|
||||
<polygon fill="white" stroke="transparent" points="112.26,-347 112.26,-435 306.26,-435 306.26,-347 112.26,-347"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="113.26,-413 113.26,-434 305.26,-434 305.26,-413 113.26,-413"/>
|
||||
<text text-anchor="start" x="182.76" y="-422" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="192.76" y="-422" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Bus    </text>
|
||||
<text text-anchor="start" x="115.26" y="-405.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.26" y="-405.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="133.26" y="-405.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="198.26" y="-405.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="208.26" y="-405.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="246.26" y="-405.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.26" y="-392.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.26" y="-392.6" font-family="Roboto" font-weight="bold" font-size="8.00">wei</text>
|
||||
<text text-anchor="start" x="138.26" y="-392.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="198.26" y="-392.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="208.26" y="-392.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (club_ptr)</text>
|
||||
<text text-anchor="start" x="293.26" y="-392.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.26" y="-379.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.26" y="-379.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">description</text>
|
||||
<text text-anchor="start" x="164.26" y="-379.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="198.26" y="-379.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="208.26" y="-379.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">TextField</text>
|
||||
<text text-anchor="start" x="240.26" y="-379.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.26" y="-366.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.26" y="-366.6" font-family="Roboto" font-size="8.00">information_json</text>
|
||||
<text text-anchor="start" x="184.26" y="-366.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="198.26" y="-366.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="208.26" y="-366.6" font-family="Roboto" font-size="8.00">TextField</text>
|
||||
<text text-anchor="start" x="240.26" y="-366.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="115.26" y="-353.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="125.26" y="-353.6" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="146.26" y="-353.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="198.26" y="-353.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="208.26" y="-353.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="243.26" y="-353.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="112.26,-347 112.26,-435 306.26,-435 306.26,-347 112.26,-347"/>
|
||||
</g>
|
||||
<!-- wei_models_Bus->wei_models_WEIClub -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>wei_models_Bus->wei_models_WEIClub</title>
|
||||
<path fill="none" stroke="black" d="M298.21,-338.76C321.62,-325.27 346.55,-310.91 368.84,-298.07"/>
|
||||
<ellipse fill="black" stroke="black" cx="294.46" cy="-340.92" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="353.26" y="-318.6" font-family="Roboto" font-size="8.00"> wei (buses)</text>
|
||||
</g>
|
||||
<!-- wei_models_BusTeam -->
|
||||
<g id="node3" class="node">
|
||||
<title>wei_models_BusTeam</title>
|
||||
<polygon fill="white" stroke="transparent" points="129.76,-562 129.76,-650 288.76,-650 288.76,-562 129.76,-562"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="131.26,-628 131.26,-649 288.26,-649 288.26,-628 131.26,-628"/>
|
||||
<text text-anchor="start" x="170.76" y="-637" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="180.76" y="-637" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    BusTeam    </text>
|
||||
<text text-anchor="start" x="133.26" y="-620.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="143.26" y="-620.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="151.26" y="-620.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="196.26" y="-620.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="206.26" y="-620.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="244.26" y="-620.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="133.26" y="-607.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="143.26" y="-607.6" font-family="Roboto" font-weight="bold" font-size="8.00">bus</text>
|
||||
<text text-anchor="start" x="159.26" y="-607.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="196.26" y="-607.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="206.26" y="-607.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="267.26" y="-607.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="133.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="143.26" y="-594.6" font-family="Roboto" font-size="8.00">color</text>
|
||||
<text text-anchor="start" x="161.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="196.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="206.26" y="-594.6" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="276.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="133.26" y="-581.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="143.26" y="-581.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">description</text>
|
||||
<text text-anchor="start" x="182.26" y="-581.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="196.26" y="-581.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="206.26" y="-581.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">TextField</text>
|
||||
<text text-anchor="start" x="238.26" y="-581.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="133.26" y="-568.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="143.26" y="-568.6" font-family="Roboto" font-size="8.00">name</text>
|
||||
<text text-anchor="start" x="164.26" y="-568.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="196.26" y="-568.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="206.26" y="-568.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="241.26" y="-568.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="129.76,-562 129.76,-650 288.76,-650 288.76,-562 129.76,-562"/>
|
||||
</g>
|
||||
<!-- wei_models_BusTeam->wei_models_Bus -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>wei_models_BusTeam->wei_models_Bus</title>
|
||||
<path fill="none" stroke="black" d="M209.26,-549.93C209.26,-515.64 209.26,-471.98 209.26,-439.26"/>
|
||||
<ellipse fill="black" stroke="black" cx="209.26" cy="-553.99" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="232.26" y="-464.1" font-family="Roboto" font-size="8.00"> bus (teams)</text>
|
||||
</g>
|
||||
<!-- wei_models_WEIRole -->
|
||||
<g id="node4" class="node">
|
||||
<title>wei_models_WEIRole</title>
|
||||
<polygon fill="white" stroke="transparent" points="589.76,-588 589.76,-624 746.76,-624 746.76,-588 589.76,-588"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="591.26,-602 591.26,-623 746.26,-623 746.26,-602 591.26,-602"/>
|
||||
<text text-anchor="start" x="631.26" y="-611" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="641.26" y="-611" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    WEIRole    </text>
|
||||
<text text-anchor="start" x="593.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="603.26" y="-594.6" font-family="Roboto" font-weight="bold" font-size="8.00">role_ptr</text>
|
||||
<text text-anchor="start" x="634.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="648.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="658.26" y="-594.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="734.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="589.76,-588 589.76,-624 746.76,-624 746.76,-588 589.76,-588"/>
|
||||
</g>
|
||||
<!-- permission_models_Role -->
|
||||
<g id="node10" class="node">
|
||||
<title>permission_models_Role</title>
|
||||
<polygon fill="white" stroke="transparent" points="667.26,-380.5 667.26,-401.5 711.26,-401.5 711.26,-380.5 667.26,-380.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="667.26,-380 667.26,-401 711.26,-401 711.26,-380 667.26,-380"/>
|
||||
<text text-anchor="start" x="671.76" y="-388.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="676.76" y="-388.4" font-family="Roboto" font-size="12.00" fill="white">Role</text>
|
||||
<text text-anchor="start" x="701.76" y="-388.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- wei_models_WEIRole->permission_models_Role -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>wei_models_WEIRole->permission_models_Role</title>
|
||||
<path fill="none" stroke="black" d="M663.01,-583.63C656.73,-554.58 648.26,-501.21 658.26,-457 661.3,-443.57 667.46,-429.66 673.5,-418.17"/>
|
||||
<polygon fill="none" stroke="black" points="676.71,-419.6 678.47,-409.16 670.57,-416.23 676.71,-419.6"/>
|
||||
<text text-anchor="middle" x="678.26" y="-468.6" font-family="Roboto" font-size="8.00"> multi-table</text>
|
||||
<text text-anchor="middle" x="678.26" y="-459.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- wei_models_WEIRegistration -->
|
||||
<g id="node5" class="node">
|
||||
<title>wei_models_WEIRegistration</title>
|
||||
<polygon fill="white" stroke="transparent" points="323.26,-503.5 323.26,-708.5 555.26,-708.5 555.26,-503.5 323.26,-503.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="324.26,-686 324.26,-707 554.26,-707 554.26,-686 324.26,-686"/>
|
||||
<text text-anchor="start" x="384.26" y="-695" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="394.26" y="-695" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    WEIRegistration    </text>
|
||||
<text text-anchor="start" x="326.26" y="-678.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-678.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="344.26" y="-678.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-678.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-678.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="495.26" y="-678.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-665.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-665.6" font-family="Roboto" font-weight="bold" font-size="8.00">user</text>
|
||||
<text text-anchor="start" x="354.26" y="-665.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-665.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-665.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="518.26" y="-665.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-652.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-652.6" font-family="Roboto" font-weight="bold" font-size="8.00">wei</text>
|
||||
<text text-anchor="start" x="349.26" y="-652.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-652.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-652.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (club_ptr)</text>
|
||||
<text text-anchor="start" x="542.26" y="-652.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-639.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-639.6" font-family="Roboto" font-size="8.00">birth_date</text>
|
||||
<text text-anchor="start" x="373.26" y="-639.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-639.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-639.6" font-family="Roboto" font-size="8.00">DateField</text>
|
||||
<text text-anchor="start" x="491.26" y="-639.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-626.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-626.6" font-family="Roboto" font-size="8.00">caution_check</text>
|
||||
<text text-anchor="start" x="387.26" y="-626.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-626.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-626.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="503.26" y="-626.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-613.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-613.6" font-family="Roboto" font-size="8.00">clothing_cut</text>
|
||||
<text text-anchor="start" x="379.26" y="-613.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-613.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-613.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="492.26" y="-613.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-600.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-600.6" font-family="Roboto" font-size="8.00">clothing_size</text>
|
||||
<text text-anchor="start" x="382.26" y="-600.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-600.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-600.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="492.26" y="-600.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-587.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-587.6" font-family="Roboto" font-size="8.00">emergency_contact_name</text>
|
||||
<text text-anchor="start" x="431.26" y="-587.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-587.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-587.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="492.26" y="-587.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-574.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-574.6" font-family="Roboto" font-size="8.00">emergency_contact_phone</text>
|
||||
<text text-anchor="start" x="433.26" y="-574.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-574.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-574.6" font-family="Roboto" font-size="8.00">PhoneNumberField</text>
|
||||
<text text-anchor="start" x="527.26" y="-574.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-561.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-561.6" font-family="Roboto" font-size="8.00">first_year</text>
|
||||
<text text-anchor="start" x="370.26" y="-561.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-561.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-561.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="503.26" y="-561.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-548.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-548.6" font-family="Roboto" font-size="8.00">gender</text>
|
||||
<text text-anchor="start" x="362.26" y="-548.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-548.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-548.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="492.26" y="-548.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-535.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-535.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">health_issues</text>
|
||||
<text text-anchor="start" x="385.26" y="-535.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-535.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-535.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">TextField</text>
|
||||
<text text-anchor="start" x="489.26" y="-535.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-522.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-522.6" font-family="Roboto" font-size="8.00">information_json</text>
|
||||
<text text-anchor="start" x="395.26" y="-522.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-522.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-522.6" font-family="Roboto" font-size="8.00">TextField</text>
|
||||
<text text-anchor="start" x="489.26" y="-522.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="326.26" y="-509.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="336.26" y="-509.6" font-family="Roboto" font-size="8.00">soge_credit</text>
|
||||
<text text-anchor="start" x="377.26" y="-509.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="447.26" y="-509.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="457.26" y="-509.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="503.26" y="-509.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="323.26,-503.5 323.26,-708.5 555.26,-708.5 555.26,-503.5 323.26,-503.5"/>
|
||||
</g>
|
||||
<!-- wei_models_WEIRegistration->wei_models_WEIClub -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>wei_models_WEIRegistration->wei_models_WEIClub</title>
|
||||
<path fill="none" stroke="black" d="M439.26,-491.29C439.26,-424.89 439.26,-345.15 439.26,-298.05"/>
|
||||
<ellipse fill="black" stroke="black" cx="439.26" cy="-495.32" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="460.76" y="-389.1" font-family="Roboto" font-size="8.00"> wei (users)</text>
|
||||
</g>
|
||||
<!-- django_contrib_auth_models_User -->
|
||||
<g id="node11" class="node">
|
||||
<title>django_contrib_auth_models_User</title>
|
||||
<polygon fill="white" stroke="transparent" points="905.26,-380.5 905.26,-401.5 949.26,-401.5 949.26,-380.5 905.26,-380.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="905.26,-380 905.26,-401 949.26,-401 949.26,-380 905.26,-380"/>
|
||||
<text text-anchor="start" x="909.26" y="-388.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
<text text-anchor="start" x="914.26" y="-388.4" font-family="Roboto" font-size="12.00" fill="white">User</text>
|
||||
<text text-anchor="start" x="940.26" y="-388.4" font-family="Roboto" font-size="8.00">  </text>
|
||||
</g>
|
||||
<!-- wei_models_WEIRegistration->django_contrib_auth_models_User -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>wei_models_WEIRegistration->django_contrib_auth_models_User</title>
|
||||
<path fill="none" stroke="black" d="M565.81,-495.81C567.95,-494.83 570.1,-493.89 572.26,-493 663.38,-455.48 699.65,-505.8 793.26,-475 837.19,-460.54 881.52,-429 906.57,-409.23"/>
|
||||
<ellipse fill="black" stroke="black" cx="562.18" cy="-497.57" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="852.76" y="-464.1" font-family="Roboto" font-size="8.00"> user (wei)</text>
|
||||
</g>
|
||||
<!-- wei_models_WEIMembership -->
|
||||
<g id="node6" class="node">
|
||||
<title>wei_models_WEIMembership</title>
|
||||
<polygon fill="white" stroke="transparent" points="196.26,-777 196.26,-852 386.26,-852 386.26,-777 196.26,-777"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="197.26,-829.5 197.26,-850.5 385.26,-850.5 385.26,-829.5 197.26,-829.5"/>
|
||||
<text text-anchor="start" x="235.76" y="-838.5" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="245.76" y="-838.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    WEIMembership    </text>
|
||||
<text text-anchor="start" x="199.26" y="-822.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="209.26" y="-822.1" font-family="Roboto" font-weight="bold" font-size="8.00">membership_ptr</text>
|
||||
<text text-anchor="start" x="273.26" y="-822.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="287.26" y="-822.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="297.26" y="-822.1" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="373.26" y="-822.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="199.26" y="-809.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="209.26" y="-809.1" font-family="Roboto" font-weight="bold" font-size="8.00">bus</text>
|
||||
<text text-anchor="start" x="225.26" y="-809.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="287.26" y="-809.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="297.26" y="-809.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="358.26" y="-809.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="199.26" y="-796.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="209.26" y="-796.1" font-family="Roboto" font-weight="bold" font-size="8.00" fill="#7b7b7b">registration</text>
|
||||
<text text-anchor="start" x="255.26" y="-796.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="287.26" y="-796.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="297.26" y="-796.1" font-family="Roboto" font-weight="bold" font-size="8.00" fill="#7b7b7b">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="373.26" y="-796.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="199.26" y="-783.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="209.26" y="-783.1" font-family="Roboto" font-weight="bold" font-size="8.00" fill="#7b7b7b">team</text>
|
||||
<text text-anchor="start" x="228.26" y="-783.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="287.26" y="-783.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="297.26" y="-783.1" font-family="Roboto" font-weight="bold" font-size="8.00" fill="#7b7b7b">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="358.26" y="-783.1" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="196.26,-777 196.26,-852 386.26,-852 386.26,-777 196.26,-777"/>
|
||||
</g>
|
||||
<!-- wei_models_WEIMembership->wei_models_Bus -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>wei_models_WEIMembership->wei_models_Bus</title>
|
||||
<path fill="none" stroke="black" d="M180.12,-800.54C128.6,-788.33 71.89,-764.71 40.26,-719 -16.9,-636.4 -9.31,-580.36 40.26,-493 54.76,-467.43 78.6,-447.65 103.85,-432.64"/>
|
||||
<ellipse fill="black" stroke="black" cx="184.11" cy="-801.44" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="76.26" y="-604.1" font-family="Roboto" font-size="8.00"> bus (memberships)</text>
|
||||
</g>
|
||||
<!-- wei_models_WEIMembership->wei_models_BusTeam -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>wei_models_WEIMembership->wei_models_BusTeam</title>
|
||||
<path fill="none" stroke="black" d="M271.95,-764.88C258.68,-731.47 241.16,-687.33 228.01,-654.22"/>
|
||||
<ellipse fill="black" stroke="black" cx="273.58" cy="-768.98" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="305.76" y="-744.1" font-family="Roboto" font-size="8.00"> team (memberships)</text>
|
||||
</g>
|
||||
<!-- wei_models_WEIMembership->wei_models_WEIRegistration -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>wei_models_WEIMembership->wei_models_WEIRegistration</title>
|
||||
<path fill="none" stroke="black" d="M332.93,-772.99C338.3,-767.14 343.55,-761.04 348.26,-755 358.55,-741.79 368.65,-727.36 378.17,-712.85"/>
|
||||
<text text-anchor="middle" x="407.26" y="-744.1" font-family="Roboto" font-size="8.00"> registration (membership)</text>
|
||||
</g>
|
||||
<!-- member_models_Membership -->
|
||||
<g id="node9" class="node">
|
||||
<title>member_models_Membership</title>
|
||||
<polygon fill="white" stroke="transparent" points="780.76,-555.5 780.76,-656.5 937.76,-656.5 937.76,-555.5 780.76,-555.5"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="782.26,-634 782.26,-655 937.26,-655 937.26,-634 782.26,-634"/>
|
||||
<text text-anchor="start" x="813.76" y="-643" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="823.76" y="-643" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Membership    </text>
|
||||
<text text-anchor="start" x="784.26" y="-626.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="794.26" y="-626.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="802.26" y="-626.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="845.26" y="-626.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="855.26" y="-626.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="893.26" y="-626.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="784.26" y="-613.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="794.26" y="-613.6" font-family="Roboto" font-weight="bold" font-size="8.00">club</text>
|
||||
<text text-anchor="start" x="812.26" y="-613.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="845.26" y="-613.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="855.26" y="-613.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="916.26" y="-613.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="784.26" y="-600.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="794.26" y="-600.6" font-family="Roboto" font-weight="bold" font-size="8.00">user</text>
|
||||
<text text-anchor="start" x="812.26" y="-600.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="845.26" y="-600.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="855.26" y="-600.6" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
|
||||
<text text-anchor="start" x="916.26" y="-600.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="784.26" y="-587.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="794.26" y="-587.6" font-family="Roboto" font-size="8.00">date_end</text>
|
||||
<text text-anchor="start" x="828.26" y="-587.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="845.26" y="-587.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="855.26" y="-587.6" font-family="Roboto" font-size="8.00">DateField</text>
|
||||
<text text-anchor="start" x="889.26" y="-587.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="784.26" y="-574.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="794.26" y="-574.6" font-family="Roboto" font-size="8.00">date_start</text>
|
||||
<text text-anchor="start" x="831.26" y="-574.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="845.26" y="-574.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="855.26" y="-574.6" font-family="Roboto" font-size="8.00">DateField</text>
|
||||
<text text-anchor="start" x="889.26" y="-574.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="784.26" y="-561.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="794.26" y="-561.6" font-family="Roboto" font-size="8.00">fee</text>
|
||||
<text text-anchor="start" x="806.26" y="-561.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="845.26" y="-561.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="855.26" y="-561.6" font-family="Roboto" font-size="8.00">PositiveIntegerField</text>
|
||||
<text text-anchor="start" x="925.26" y="-561.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="780.76,-555.5 780.76,-656.5 937.76,-656.5 937.76,-555.5 780.76,-555.5"/>
|
||||
</g>
|
||||
<!-- wei_models_WEIMembership->member_models_Membership -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>wei_models_WEIMembership->member_models_Membership</title>
|
||||
<path fill="none" stroke="black" d="M394.29,-811.98C494.78,-806.29 648.78,-786.06 763.26,-719 784.62,-706.48 803.55,-687.45 818.82,-668.6"/>
|
||||
<polygon fill="none" stroke="black" points="821.72,-670.58 825.14,-660.55 816.21,-666.25 821.72,-670.58"/>
|
||||
<text text-anchor="middle" x="749.26" y="-748.6" font-family="Roboto" font-size="8.00"> multi-table</text>
|
||||
<text text-anchor="middle" x="749.26" y="-739.6" font-family="Roboto" font-size="8.00">inheritance</text>
|
||||
</g>
|
||||
<!-- member_models_Profile -->
|
||||
<g id="node7" class="node">
|
||||
<title>member_models_Profile</title>
|
||||
<polygon fill="white" stroke="transparent" points="971.76,-497 971.76,-715 1190.76,-715 1190.76,-497 971.76,-497"/>
|
||||
<polygon fill="#1b563f" stroke="transparent" points="973.26,-693 973.26,-714 1190.26,-714 1190.26,-693 973.26,-693"/>
|
||||
<text text-anchor="start" x="1049.26" y="-702" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1059.26" y="-702" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Profile    </text>
|
||||
<text text-anchor="start" x="975.26" y="-685.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-685.6" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
|
||||
<text text-anchor="start" x="993.26" y="-685.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-685.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-685.6" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
|
||||
<text text-anchor="start" x="1127.26" y="-685.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-672.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-672.6" font-family="Roboto" font-weight="bold" font-size="8.00">user</text>
|
||||
<text text-anchor="start" x="1003.26" y="-672.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-672.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-672.6" font-family="Roboto" font-weight="bold" font-size="8.00">OneToOneField (id)</text>
|
||||
<text text-anchor="start" x="1165.26" y="-672.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-659.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-659.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">address</text>
|
||||
<text text-anchor="start" x="1013.26" y="-659.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-659.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-659.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="1124.26" y="-659.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-646.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-646.6" font-family="Roboto" font-size="8.00">department</text>
|
||||
<text text-anchor="start" x="1027.26" y="-646.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-646.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-646.6" font-family="Roboto" font-size="8.00">CharField</text>
|
||||
<text text-anchor="start" x="1124.26" y="-646.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-633.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-633.6" font-family="Roboto" font-size="8.00">email_confirmed</text>
|
||||
<text text-anchor="start" x="1044.26" y="-633.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-633.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-633.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="1135.26" y="-633.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-620.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-620.6" font-family="Roboto" font-size="8.00">last_report</text>
|
||||
<text text-anchor="start" x="1023.26" y="-620.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-620.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-620.6" font-family="Roboto" font-size="8.00">DateTimeField</text>
|
||||
<text text-anchor="start" x="1141.26" y="-620.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-607.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-607.6" font-family="Roboto" font-size="8.00">ml_art_registration</text>
|
||||
<text text-anchor="start" x="1052.26" y="-607.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-607.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-607.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="1135.26" y="-607.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-594.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">ml_events_registration</text>
|
||||
<text text-anchor="start" x="1065.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-594.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="1124.26" y="-594.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-581.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-581.6" font-family="Roboto" font-size="8.00">ml_sport_registration</text>
|
||||
<text text-anchor="start" x="1060.26" y="-581.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-581.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-581.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="1135.26" y="-581.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-568.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-568.6" font-family="Roboto" font-size="8.00">paid</text>
|
||||
<text text-anchor="start" x="1001.26" y="-568.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-568.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-568.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="1135.26" y="-568.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-555.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-555.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">phone_number</text>
|
||||
<text text-anchor="start" x="1040.26" y="-555.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-555.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-555.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">PhoneNumberField</text>
|
||||
<text text-anchor="start" x="1159.26" y="-555.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-542.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-542.6" font-family="Roboto" font-size="8.00">promotion</text>
|
||||
<text text-anchor="start" x="1021.26" y="-542.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-542.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-542.6" font-family="Roboto" font-size="8.00">PositiveSmallIntegerField</text>
|
||||
<text text-anchor="start" x="1178.26" y="-542.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-529.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-529.6" font-family="Roboto" font-size="8.00">registration_valid</text>
|
||||
<text text-anchor="start" x="1045.26" y="-529.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-529.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-529.6" font-family="Roboto" font-size="8.00">BooleanField</text>
|
||||
<text text-anchor="start" x="1135.26" y="-529.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-516.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-516.6" font-family="Roboto" font-size="8.00">report_frequency</text>
|
||||
<text text-anchor="start" x="1046.26" y="-516.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-516.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-516.6" font-family="Roboto" font-size="8.00">PositiveSmallIntegerField</text>
|
||||
<text text-anchor="start" x="1178.26" y="-516.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="975.26" y="-503.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="985.26" y="-503.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">section</text>
|
||||
<text text-anchor="start" x="1010.26" y="-503.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1079.26" y="-503.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<text text-anchor="start" x="1089.26" y="-503.6" font-family="Roboto" font-size="8.00" fill="#7b7b7b">CharField</text>
|
||||
<text text-anchor="start" x="1124.26" y="-503.6" font-family="Roboto" font-size="8.00">    </text>
|
||||
<polygon fill="none" stroke="black" points="971.76,-497 971.76,-715 1190.76,-715 1190.76,-497 971.76,-497"/>
|
||||
</g>
|
||||
<!-- member_models_Profile->django_contrib_auth_models_User -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>member_models_Profile->django_contrib_auth_models_User</title>
|
||||
<path fill="none" stroke="black" d="M1010.97,-492.62C1002.57,-480.41 993.9,-468.32 985.26,-457 972.23,-439.93 955.81,-421.8 943.77,-409.03"/>
|
||||
<text text-anchor="middle" x="1020.26" y="-464.1" font-family="Roboto" font-size="8.00"> user (profile)</text>
|
||||
</g>
|
||||
<!-- member_models_Club->member_models_Club -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>member_models_Club->member_models_Club</title>
|
||||
<path fill="none" stroke="black" d="M779.77,-93.53C786.05,-90.32 789.76,-85.97 789.76,-80.5 789.76,-73.05 782.88,-67.69 772.02,-64.43"/>
|
||||
<ellipse fill="black" stroke="black" cx="775.74" cy="-95.11" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="823.26" y="-78.6" font-family="Roboto" font-size="8.00"> parent_club (club)</text>
|
||||
</g>
|
||||
<!-- member_models_Membership->member_models_Club -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>member_models_Membership->member_models_Club</title>
|
||||
<path fill="none" stroke="black" d="M863.76,-543.26C864.15,-516.37 862.46,-484.73 855.26,-457 826.89,-347.74 762.07,-234.51 714.72,-161.34"/>
|
||||
<ellipse fill="black" stroke="black" cx="863.67" cy="-547.45" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="841.26" y="-318.6" font-family="Roboto" font-size="8.00"> club (membership)</text>
|
||||
</g>
|
||||
<!-- member_models_Membership->permission_models_Role -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>member_models_Membership->permission_models_Role</title>
|
||||
<path fill="none" stroke="black" d="M816.49,-545.05C801.23,-526.63 782.92,-507.44 763.26,-493 746.89,-480.98 735.35,-489.62 721.26,-475 705.92,-459.09 697.73,-435.03 693.49,-416.91"/>
|
||||
<ellipse fill="black" stroke="black" cx="819.01" cy="-548.15" rx="4" ry="4"/>
|
||||
<ellipse fill="black" stroke="black" cx="692.65" cy="-412.96" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="757.26" y="-464.1" font-family="Roboto" font-size="8.00"> roles (membership)</text>
|
||||
</g>
|
||||
<!-- member_models_Membership->django_contrib_auth_models_User -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>member_models_Membership->django_contrib_auth_models_User</title>
|
||||
<path fill="none" stroke="black" d="M878.91,-543.46C893.51,-497.7 912.45,-438.39 921.69,-409.44"/>
|
||||
<ellipse fill="black" stroke="black" cx="877.63" cy="-547.44" rx="4" ry="4"/>
|
||||
<text text-anchor="middle" x="943.76" y="-464.1" font-family="Roboto" font-size="8.00"> user (memberships)</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 58 KiB |