Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
Benjamin Dauvergne | 5a93ab71f0 | |
Pierre Cros | 08c7e6e1c3 | |
Benjamin Dauvergne | b241e6dfdc | |
Benjamin Dauvergne | cbb1e30389 | |
Benjamin Dauvergne | 3c8a3fdc58 | |
Benjamin Dauvergne | 0123c4e678 | |
Benjamin Dauvergne | db02708d44 | |
Benjamin Dauvergne | c3b2d05505 |
|
@ -1,5 +0,0 @@
|
|||
[run]
|
||||
dynamic_context = test_function
|
||||
|
||||
[html]
|
||||
show_contexts = True
|
|
@ -1,6 +0,0 @@
|
|||
# misc: apply double-quote-string-fixer (#79788)
|
||||
2489ed708e466f94fcddaeb46a196be9d8963414
|
||||
# ci: fix remaining ruff warnings (#86370)
|
||||
98dccb4964b4bb1dcc33ebbd78461648abdd93a4
|
||||
# ci: apply pre-commit hooks (#86370)
|
||||
229479409e3534c388c4a6083c476ea9d606d7a8
|
|
@ -1,7 +1,2 @@
|
|||
local_settings.py
|
||||
archive/
|
||||
*.pyc
|
||||
mbox/
|
||||
media/
|
||||
/docbow.db
|
||||
/docbow-venv
|
||||
*.mo
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: double-quote-string-fixer
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ['--keep-percent-format', '--py39-plus']
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: 1.13.0
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: ['--target-version', '3.2']
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: ['--target-version', 'py39', '--skip-string-normalization', '--line-length', '110']
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ['--profile', 'black', '--line-length', '110']
|
||||
- repo: https://github.com/rtts/djhtml
|
||||
rev: '3.0.5'
|
||||
hooks:
|
||||
- id: djhtml
|
||||
args: ['--tabwidth', '2']
|
||||
- repo: https://git.entrouvert.org/pre-commit-debian.git
|
||||
rev: v0.3
|
||||
hooks:
|
||||
- id: pre-commit-debian
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.1.15
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
exclude: 'debian/.*|benchmark'
|
||||
args: [ --fix ]
|
|
@ -1,53 +0,0 @@
|
|||
@Library('eo-jenkins-lib@main') import eo.Utils
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
options { disableConcurrentBuilds() }
|
||||
stages {
|
||||
stage('Unit Tests') {
|
||||
steps {
|
||||
sh 'tox -rv'
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
utils = new Utils()
|
||||
utils.publish_coverage('coverage.xml')
|
||||
utils.publish_coverage_native('index.html')
|
||||
}
|
||||
mergeJunitResults()
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Packaging') {
|
||||
steps {
|
||||
script {
|
||||
env.SHORT_JOB_NAME=sh(
|
||||
returnStdout: true,
|
||||
// given JOB_NAME=gitea/project/PR-46, returns project
|
||||
// given JOB_NAME=project/main, returns project
|
||||
script: '''
|
||||
echo "${JOB_NAME}" | sed "s/gitea\\///" | awk -F/ '{print $1}'
|
||||
'''
|
||||
).trim()
|
||||
if (env.GIT_BRANCH == 'main' || env.GIT_BRANCH == 'origin/main') {
|
||||
sh "sudo -H -u eobuilder /usr/local/bin/eobuilder -d bullseye,bookworm ${SHORT_JOB_NAME}"
|
||||
} else if (env.GIT_BRANCH.startsWith('hotfix/')) {
|
||||
sh "sudo -H -u eobuilder /usr/local/bin/eobuilder -d bullseye,bookworm --branch ${env.GIT_BRANCH} --hotfix ${SHORT_JOB_NAME}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
utils = new Utils()
|
||||
utils.mail_notify(currentBuild, env, 'ci+jenkins-docbow@entrouvert.org')
|
||||
}
|
||||
}
|
||||
success {
|
||||
cleanWs()
|
||||
}
|
||||
}
|
||||
}
|
21
MANIFEST.in
21
MANIFEST.in
|
@ -1,21 +1,12 @@
|
|||
recursive-include tools *
|
||||
recursive-include help Makefile *.png *.page
|
||||
recursive-include docbow_project/locale *.po *.mo
|
||||
recursive-include docbow_project/locale *.po
|
||||
recursive-include docbow_project/templates *.html
|
||||
recursive-include docbow_project/docbow/fixtures *.json
|
||||
recursive-include docbow_project/docbow/static *.css *.gif *.html *.jpg *.js *.png *.svg
|
||||
recursive-include docbow_project/docbow/templates *.html *.txt *.js
|
||||
recursive-include docbow_project/pw/static *.css *.png
|
||||
recursive-include docbow_project/pw/templates *.html
|
||||
recursive-include docbow_project/pw/locale *.po *.mo
|
||||
recursive-include docbow_project/pw/fixtures *.json
|
||||
recursive-include docbow_project/pfwb/static *.css *.png *.svg
|
||||
recursive-include docbow_project/pfwb/templates *.html
|
||||
recursive-include docbow_project/pfwb/locale *.po *.mo
|
||||
include docbow_project/docbow/allkeys.txt
|
||||
include docbow_project/docbow/*.crt
|
||||
include docbow_project/pfwb/README.txt
|
||||
recursive-include docbow_project/docbow/static *.css *.gif *.html *.jpg *.js *.php *.png *.txt *.xml
|
||||
recursive-include docbow_project/docbow/templates *.html
|
||||
include COPYING
|
||||
include Makefile
|
||||
include dev-req.txt
|
||||
include README.rst
|
||||
include MANIFEST.in
|
||||
prune media
|
||||
prune static
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
HOST=$(shell uname -n)
|
||||
|
||||
|
||||
update:
|
||||
cd source/ && git pull
|
||||
cd source/docbow_project && python manage.py collectstatic
|
||||
cd source/docbow_project && python manage.py compilemessages
|
||||
cd source/docbow_project && python manage.py syncdb --migrate
|
||||
pip install -r source/dev-req.txt
|
||||
/etc/init.d/courrier.parlement-wallon.be reload
|
||||
ifeq ($(HOST),docbow1.entrouvert.com)
|
||||
rsync -avz source/ docbow@docbow2-internal:/home/docbow/source/
|
||||
rsync -avz env/ docbow@docbow2-internal:/home/docbow/env/
|
||||
else
|
||||
rsync -avz source/ docbow@docbow1-internal:/home/docbow/source/
|
||||
rsync -avz env/ docbow@docbow1-internal:/home/docbow/env/
|
||||
endif
|
236
README.rst
236
README.rst
|
@ -11,19 +11,6 @@ docbow. The user running the application need access to this database. On most
|
|||
Unix system it means creating a postgresql user with the same name as the Unix
|
||||
user it the database server runs on the same host.
|
||||
|
||||
Installation for developpers
|
||||
----------------------------
|
||||
|
||||
- First run:
|
||||
|
||||
./start.sh
|
||||
|
||||
- and next times:
|
||||
|
||||
./run.sh
|
||||
|
||||
- To update dependencies in the virtualenv environment rerun ./start.sh
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
|
@ -39,8 +26,8 @@ Installation
|
|||
python-ldap, for them you will some developement package installed, on
|
||||
Debian, it means gcc, python-dev, libssl-dev, libldap-dev, libsasl2-dev,
|
||||
libpq-dev)::
|
||||
./getlasso.sh
|
||||
pip install -e .
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
- Create user and database (you can replace $USER by www-data for a WSGI
|
||||
installation)::
|
||||
|
@ -48,35 +35,29 @@ Installation
|
|||
sudo -u postgres createuser $USER
|
||||
sudo -u postgres createdb -O $USER docbow
|
||||
|
||||
- Fill in your local settings
|
||||
Rename locale_settings.py.example to local_settings.py, and fill it
|
||||
with appropriate database informations (standard Django settings.py file like)
|
||||
Also put DEBUG to True or set the SECRET_KEY
|
||||
|
||||
- Create database schema and load initial data::
|
||||
|
||||
docbow-ctl syncdb
|
||||
docbow-ctl loaddata content filetype groups
|
||||
( cd docbow_project; python manage.py syncdb --migrate )
|
||||
( cd docbow_project; python manage.py loaddata content filetype groups )
|
||||
|
||||
- Compile UI strings translations::
|
||||
|
||||
docbow-ctl makemessages --all
|
||||
docbow-ctl compilemessages
|
||||
( cd docbow_project; python manage.py compilemessages )
|
||||
|
||||
- Load user list (you need to get a user list as a CSV files, see format later)::
|
||||
|
||||
( cd docbow_project; docbow-ctl load-users-csv users.csv )
|
||||
( cd docbow_project; python manage.py load-users-csv users.csv )
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
When you upgrade you must execute this from the root directory:
|
||||
|
||||
( cd docbow_project; docbow-ctl syncdb --migrate )
|
||||
( cd docbow_project; python manage.py syncdb --migrate )
|
||||
|
||||
if you application run as another user (www-data for example if using WSGI)::
|
||||
|
||||
( cd docbow_project; sudo -u www-data docbow-ctl syncdb --migrate )
|
||||
( cd docbow_project; sudo -u www-data python manage.py syncdb --migrate )
|
||||
|
||||
Installation on Debian with Apache2/mod_wsgi
|
||||
--------------------------------------------
|
||||
|
@ -89,7 +70,7 @@ Now you can follow the generic installation described before.
|
|||
|
||||
Collect all static content::
|
||||
|
||||
(cd docbow_project; docbow-ctl collectstatic)
|
||||
(cd docbow_project; python manage.py collectstatic)
|
||||
|
||||
After that you must install apache2 and the mod_wsgi module. On Debian do::
|
||||
|
||||
|
@ -135,7 +116,7 @@ Install gunicorn::
|
|||
|
||||
Collect all static content::
|
||||
|
||||
(cd docbow_project; docbow-ctl collectstatic)
|
||||
(cd docbow_project; python manage.py collectstatic)
|
||||
|
||||
After that you must install apache2 and the mod_wsgi module. On Debian do::
|
||||
|
||||
|
@ -168,200 +149,3 @@ docbow postgresql database)::
|
|||
You must set a charset to use as file are created with name recevied from user
|
||||
agents which can contain any unicode character. The locale "C.UTF-8" works in
|
||||
this case.
|
||||
|
||||
Configuring main menu
|
||||
---------------------
|
||||
|
||||
To configure the main menu you can setup the DOCBOW_MENU variable in your
|
||||
`local_settings.py` file. The default setting is: ::
|
||||
|
||||
DOCBOW_MENU = [
|
||||
('send-file', gettext_noop('send-file_menu')),
|
||||
('inbox', gettext_noop('inbox_menu')),
|
||||
('outbox', gettext_noop('outbox_menu')),
|
||||
('docbow_admin:index', gettext_noop('admin_menu')),
|
||||
('profile', gettext_noop('profile_menu')),
|
||||
('auth_password_change', gettext_noop('password_change_menu')),
|
||||
('delegate', gettext_noop('delegate_menu')),
|
||||
('mailing-lists', gettext_noop('mailing-lists')),
|
||||
('help', gettext_noop('help_menu')),
|
||||
('contact', gettext_noop('contact_menu')),
|
||||
]
|
||||
|
||||
The first element of each pair must be a django view name of an URL, the second
|
||||
one is potentially localized, for example to add a link to google, add this line::
|
||||
|
||||
('http://google.com/', u'Google'),
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
All settings must be donne in the file ``/etc/docbow/local_settings.py``. Available settings are:
|
||||
|
||||
* ``DOCBOW_ORGANIZATION``: an unicode string giving a description of the
|
||||
organization providing the platform. It's used as a signature in mail and
|
||||
sms notifications.
|
||||
* ``DOCBOW_BASE_URL``: the base URL of the application. It's used for building
|
||||
URL in notifications, emails or SMS.
|
||||
* ``DOCBOW_MENU``: description of the left column menu; see previous section
|
||||
for a description and the default value.
|
||||
* ``DOCBOW_MAILBOX_PER_PAGE``: the number of message to show on listing pages.
|
||||
Default is 20.
|
||||
* ``RAVEN_CONFIG_DSN``: the URL of the sentry project for gathering exceptions.
|
||||
* ``DOCBOW_MAX_FILE_SIZE``: the maximum file size for attached files, as
|
||||
bytes. Default is 10 Mo.
|
||||
* ``DOCBOW_TRUNCATE_FILENAME``: the maximum length for filenames. Default is
|
||||
80 unicode characters (codepoints).
|
||||
* ``DOCBOW_PERSONAL_EMAIL``: allow user to have a personal email,
|
||||
notifications will be sent to their institutional email and personal email.
|
||||
* ``DOCBOW_MOBILE_PHONE``: allow user to set a mobile phone number
|
||||
to receive notification by SMS.
|
||||
* ``GROUP_LISTING``: whether to show the link to the list of
|
||||
mailing lists in the left menu.
|
||||
* ``DOCBOW_PRIVATE_DOCUMENTS``: add a private checkbox to the sending form,
|
||||
when checked the document is only display to direct recipients and not to
|
||||
delegates,
|
||||
|
||||
Customizing templates
|
||||
---------------------
|
||||
|
||||
You can override any template by copying it from
|
||||
``docbow_project/docbow/templates/`` to ``/var/lib/docbow/templates/`` and then
|
||||
modifying it. If you want to provide static ressources you can put them in
|
||||
``/var/lib/docbow/extra_static/`` and update the static files cache with: ::
|
||||
|
||||
./docbow-ctl collectstatic
|
||||
|
||||
Sending a file from the command line
|
||||
------------------------------------
|
||||
|
||||
The ``sendfile`` command is used to send a file from the command line. Basic
|
||||
usage is::
|
||||
|
||||
|
||||
./docbow-ctl sendfile --sender bdauvergne --to-user pcros --to-list liste1 \
|
||||
--description "New question" file1 file2
|
||||
|
||||
The sender and filetype parameters are mandatory. There must be at least one
|
||||
recipient, list or user, and one file attached to the sending.
|
||||
|
||||
Listing users from the command line
|
||||
-----------------------------------
|
||||
|
||||
The ``list-users`` command is used to list the user registerd in docbow. The
|
||||
default output format is an ASCII table. You can ask for CSV output using the
|
||||
``--csv`` parameter.
|
||||
|
||||
Example output::
|
||||
|
||||
| Id | Username | First name | Last name | Email | Mobile phone | Personal mail | Lists | Groups |
|
||||
| 502 | test3 | Thomas | Noël | bob@gmail.com | | | Liste test2 | |
|
||||
| 503 | bdauvergne-1 | benjamin | dauvergne | test@gmail.com | | | | |
|
||||
| 501 | test2 | Kévin | Gaspard | test2@gmail.com | | | Liste test1 | |
|
||||
| 504 | 503 | | | toto@example.com | | | | |
|
||||
| 444 | test | Jérôme | Schneider | john.doe@gmail.com | | | Liste test1 | |
|
||||
| 98 | bdauvergne | Benjamin | Dauvergne | coin@entrouvert.com | +33630XXX893 | joe@example.com | | Administrateurs |
|
||||
|
||||
The command supports a minimal query language, for example to get all users in
|
||||
the group ``Administrator`` ::
|
||||
|
||||
|
||||
./docbow-ctl list-users groups__name=Administrator
|
||||
|
||||
Listing lists from the command line
|
||||
-----------------------------------
|
||||
|
||||
The ``list-lists`` command provide exactly the same service as ``list-users``
|
||||
but for mailing lists.
|
||||
|
||||
Example output::
|
||||
|
||||
| Id | Name | Members | List Members |
|
||||
| 54 | Liste test1 | | test,test2 |
|
||||
| 55 | Liste test2 | Liste test1 | test3 |
|
||||
| 56 | List test1 | | |
|
||||
|
||||
Adding users from the command line
|
||||
----------------------------------
|
||||
|
||||
The ``add-user`` create a new user or modify an existing user. The username can
|
||||
only be set at creation.::
|
||||
|
||||
Usage: ./docbow-ctl add-user [options] <username>
|
||||
|
||||
Create a new user or update an existing user
|
||||
|
||||
List and groups can be referred by name or by id.
|
||||
|
||||
|
||||
Options:
|
||||
-v VERBOSITY, --verbosity=VERBOSITY
|
||||
Verbosity level; 0=minimal output, 1=normal output,
|
||||
2=verbose output, 3=very verbose output
|
||||
--settings=SETTINGS The Python path to a settings module, e.g.
|
||||
"myproject.settings.main". If this isn't provided, the
|
||||
DJANGO_SETTINGS_MODULE environment variable will be
|
||||
used.
|
||||
--pythonpath=PYTHONPATH
|
||||
A directory to add to the Python path, e.g.
|
||||
"/home/djangoprojects/myproject".
|
||||
--traceback Print traceback on exception
|
||||
--first-name=FIRST_NAME
|
||||
set first name
|
||||
--last-name=LAST_NAME
|
||||
set last name
|
||||
--email=EMAIL set email
|
||||
--mobile-phone=MOBILE_PHONE
|
||||
set mobile phone used for SMS notifications
|
||||
--personal-email=PERSONAL_EMAIL
|
||||
set personal email
|
||||
--add-list=ADD_LIST add user to list
|
||||
--add-group=ADD_GROUP
|
||||
add user to group
|
||||
--remove-list=REMOVE_LIST
|
||||
remove user from list
|
||||
--remove-group=REMOVE_GROUP
|
||||
remove user from group
|
||||
--version show program's version number and exit
|
||||
-h, --help show this help message and exit
|
||||
|
||||
Adding lists from the command line
|
||||
----------------------------------
|
||||
|
||||
The ``add-list`` command allows to create new lists and to update their
|
||||
members, lists or users::
|
||||
|
||||
usage: docbow-ctl add-list [-h] [--version] [-v {0,1,2,3}]
|
||||
[--settings SETTINGS] [--pythonpath PYTHONPATH]
|
||||
[--traceback] [--no-color] [--add-list ADD_LIST]
|
||||
[--remove-list REMOVE_LIST] [--add-user ADD_USER]
|
||||
[--remove-user REMOVE_USER]
|
||||
ml_name
|
||||
|
||||
Create or update a list
|
||||
|
||||
positional arguments:
|
||||
ml_name Name of the mailing list
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--version show program's version number and exit
|
||||
-v {0,1,2,3}, --verbosity {0,1,2,3}
|
||||
Verbosity level; 0=minimal output, 1=normal output,
|
||||
2=verbose output, 3=very verbose output
|
||||
--settings SETTINGS The Python path to a settings module, e.g.
|
||||
"myproject.settings.main". If this isn't provided, the
|
||||
DJANGO_SETTINGS_MODULE environment variable will be
|
||||
used.
|
||||
--pythonpath PYTHONPATH
|
||||
A directory to add to the Python path, e.g.
|
||||
"/home/djangoprojects/myproject".
|
||||
--traceback Raise on CommandError exceptions
|
||||
--no-color Don't colorize the command output.
|
||||
--add-list ADD_LIST add a list as a sublist
|
||||
--remove-list REMOVE_LIST
|
||||
remove list as a sublist
|
||||
--add-user ADD_USER add a user member
|
||||
--remove-user REMOVE_USER
|
||||
remove a user member
|
||||
|
||||
|
|
59
benchmark
59
benchmark
|
@ -1,59 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import json
|
||||
import os.path
|
||||
import random
|
||||
import time
|
||||
|
||||
import numpy
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'docbow_project.settings')
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from docbow_project.docbow.models import FileType
|
||||
from docbow_project.docbow.views import inbox_view, outbox_view, send_file
|
||||
|
||||
|
||||
def stat(seq):
|
||||
return {
|
||||
'min': min(seq),
|
||||
'max': max(seq),
|
||||
'mean': numpy.mean(seq),
|
||||
'median': numpy.median(seq),
|
||||
'std': numpy.std(seq),
|
||||
}
|
||||
|
||||
|
||||
rf = RequestFactory()
|
||||
paths = [('/inbox/', inbox_view, {}), ('/outbox/', outbox_view, {})]
|
||||
filetypes = list(FileType.objects.all())
|
||||
random.shuffle(filetypes)
|
||||
for file_type in filetypes[:10]:
|
||||
paths.append(('/inbox/%s/' % file_type.id, send_file, {'file_type_id': file_type.id}))
|
||||
data = []
|
||||
for path, view, kwargs in paths:
|
||||
get_request = rf.get(path)
|
||||
get_request.session = {}
|
||||
print('Testing view', path, 'for all users:')
|
||||
seq = []
|
||||
for user in User.objects.all():
|
||||
get_request.user = user
|
||||
now = time.time()
|
||||
response = view(get_request, **kwargs)
|
||||
if hasattr(response, 'render'):
|
||||
response.render()
|
||||
str(response)
|
||||
duration = time.time() - now
|
||||
seq.append(duration * 1000)
|
||||
data.append(stat(seq))
|
||||
data[-1]['path'] = path
|
||||
|
||||
with file('vix.js', 'w') as f:
|
||||
f.write(
|
||||
'''var vix =
|
||||
'''
|
||||
)
|
||||
f.write(json.dumps(data, indent=4))
|
||||
f.write(';')
|
|
@ -1,5 +0,0 @@
|
|||
docbow (0.1.44.g543c8de-1) unstable; urgency=low
|
||||
|
||||
* Initial release
|
||||
|
||||
-- Benjamin Dauvergne <bdauvergne@entrouvert.com> Fri, 5 May 2014 14:31:55 +0200
|
|
@ -1,41 +0,0 @@
|
|||
server {
|
||||
listen 443;
|
||||
server_name citoyen.example.fr;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
|
||||
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
|
||||
|
||||
access_log /var/log/nginx/citoyen.example.fr-access.log combined;
|
||||
error_log /var/log/nginx/citoyen.example.fr-error.log;
|
||||
|
||||
location /static {
|
||||
alias /var/lib/portail-citoyen/static;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://unix:/var/run/portail-citoyen/portail-citoyen.sock;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-SSL on;
|
||||
proxy_set_header X-Forwarded-Protocol ssl;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name citoyen.example.fr;
|
||||
|
||||
access_log /var/log/nginx/citoyen.example.fr-access.log combined;
|
||||
error_log /var/log/nginx/citoyen.example.fr-error.log;
|
||||
|
||||
location /static {
|
||||
alias /var/lib/portail-citoyen/static;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://unix:/var/run/portail-citoyen/portail-citoyen.sock;
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
# https://raw.githubusercontent.com/file/file/master/magic/Magdir/msooxml
|
||||
#------------------------------------------------------------------------------
|
||||
# $File: msooxml,v 1.8 2018/05/24 18:11:17 christos Exp $
|
||||
# msooxml: file(1) magic for Microsoft Office XML
|
||||
# From: Ralf Brown <ralf.brown@gmail.com>
|
||||
|
||||
# .docx, .pptx, and .xlsx are XML plus other files inside a ZIP
|
||||
# archive. The first member file is normally "[Content_Types].xml".
|
||||
# but some libreoffice generated files put this later. Perhaps skip
|
||||
# the "[Content_Types].xml" test?
|
||||
# Since MSOOXML doesn't have anything like the uncompressed "mimetype"
|
||||
# file of ePub or OpenDocument, we'll have to scan for a filename
|
||||
# which can distinguish between the three types
|
||||
|
||||
0 name msooxml
|
||||
>0 string word/ Microsoft Word 2007+
|
||||
!:mime application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
>0 string ppt/ Microsoft PowerPoint 2007+
|
||||
!:mime application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
>0 string xl/ Microsoft Excel 2007+
|
||||
!:mime application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
|
||||
# start by checking for ZIP local file header signature
|
||||
0 string PK\003\004
|
||||
!:strength +10
|
||||
# make sure the first file is correct
|
||||
>0x1E use msooxml
|
||||
>0x1E regex \\[Content_Types\\]\\.xml|_rels/\\.rels
|
||||
# skip to the second local file header
|
||||
# since some documents include a 520-byte extra field following the file
|
||||
# header, we need to scan for the next header
|
||||
>>(18.l+49) search/2000 PK\003\004
|
||||
# now skip to the *third* local file header; again, we need to scan due to a
|
||||
# 520-byte extra field following the file header
|
||||
>>>&26 search/1000 PK\003\004
|
||||
# and check the subdirectory name to determine which type of OOXML
|
||||
# file we have. Correct the mimetype with the registered ones:
|
||||
# http://technet.microsoft.com/en-us/library/cc179224.aspx
|
||||
>>>>&26 use msooxml
|
||||
>>>>&26 default x
|
||||
# OpenOffice/Libreoffice orders ZIP entry differently, so check the 4th file
|
||||
>>>>>&26 search/1000 PK\003\004
|
||||
>>>>>>&26 use msooxml
|
||||
>>>>>>&26 default x Microsoft OOXML
|
|
@ -1,32 +0,0 @@
|
|||
Source: docbow
|
||||
Maintainer: Benjamin Dauvergne <bdauvergne@entrouvert.com>
|
||||
Section: python
|
||||
Priority: optional
|
||||
Build-Depends: debhelper-compat (= 12),
|
||||
dh-python,
|
||||
openssl,
|
||||
python3-all,
|
||||
python3-django (>= 1:1.11),
|
||||
python3-setuptools,
|
||||
yelp-tools,
|
||||
yelp-xsl,
|
||||
Standards-Version: 3.9.6
|
||||
Homepage: https://dev.entrouvert.org/projects/docbow
|
||||
|
||||
Package: docbow
|
||||
Architecture: all
|
||||
Suggests: postgresql,
|
||||
Depends: python3-django (>= 1:1.11),
|
||||
python3-django-journal (>= 2.0.0),
|
||||
python3-django-picklefield,
|
||||
python3-django-tables2,
|
||||
python3-django-watson (>= 1.2.0),
|
||||
python3-magic,
|
||||
python3-psycopg2,
|
||||
python3-requests,
|
||||
uwsgi,
|
||||
uwsgi-plugin-python3,
|
||||
${misc:Depends},
|
||||
${python3:Depends},
|
||||
Recommends: python3-django-mellon,
|
||||
Description: Document Box Wallone
|
|
@ -1,16 +0,0 @@
|
|||
# This file is sourced by "execfile" from docbow_project.settings
|
||||
|
||||
import glob
|
||||
import os
|
||||
|
||||
PROJECT_NAME = 'docbow'
|
||||
|
||||
ETC_DIR = os.path.join('/etc', PROJECT_NAME)
|
||||
ETC_SETTINGS_PY = os.path.join(ETC_DIR, 'settings.py')
|
||||
|
||||
|
||||
if os.path.exists(ETC_SETTINGS_PY):
|
||||
exec(open(ETC_SETTINGS_PY).read())
|
||||
|
||||
for filename in sorted(glob.glob(os.path.join(ETC_DIR, 'settings.d', '*.py'))):
|
||||
exec(open(filename).read())
|
|
@ -1,10 +0,0 @@
|
|||
etc/docbow
|
||||
etc/nginx/sites-available
|
||||
usr/lib/docbow
|
||||
usr/share/docbow/templates
|
||||
var/lib/docbow/collectstatic
|
||||
var/lib/docbow/media
|
||||
var/lib/docbow/static
|
||||
var/lib/docbow/templates
|
||||
var/log/docbow
|
||||
var/run/docbow
|
|
@ -1,25 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
NAME=docbow
|
||||
MANAGE=/usr/lib/$NAME/manage.py
|
||||
|
||||
# load Debian default configuration
|
||||
export DOCBOW_SETTINGS_FILE=/usr/lib/$NAME/debian_config.py
|
||||
|
||||
# check user
|
||||
if test x$1 = x"--forceuser"
|
||||
then
|
||||
shift
|
||||
elif test $(id -un) != "$NAME"
|
||||
then
|
||||
echo "error: must use $0 with user ${NAME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if test $# -eq 0
|
||||
then
|
||||
python3 ${MANAGE} help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 ${MANAGE} "$@"
|
|
@ -1,2 +0,0 @@
|
|||
*/1 * * * * docbow /bin/systemctl status docbow > /dev/null 2>&1 && /usr/bin/docbow-manage notify > /dev/null
|
||||
*/10 * * * * docbow /usr/bin/docbow-manage empty-trash > /dev/null
|
|
@ -1,26 +0,0 @@
|
|||
[Unit]
|
||||
Description=Docbow
|
||||
After=network.target postgresql.service
|
||||
Wants=postgresql.service
|
||||
|
||||
[Service]
|
||||
SyslogIdentifier=uwsgi/%p
|
||||
Environment=DOCBOW_SETTINGS_FILE=/usr/lib/%p/debian_config.py
|
||||
Environment=LANG=fr_FR.UTF-8
|
||||
Environment=LC_ALL=fr_FR.UTF-8
|
||||
User=%p
|
||||
Group=%p
|
||||
ExecStartPre=/usr/bin/docbow-manage migrate --noinput --verbosity 1
|
||||
ExecStartPre=/usr/bin/docbow-manage collectstatic --noinput
|
||||
ExecStart=/usr/bin/uwsgi --ini /etc/%p/uwsgi.ini
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
KillSignal=SIGQUIT
|
||||
TimeoutStartSec=0
|
||||
PrivateTmp=true
|
||||
Restart=on-failure
|
||||
RuntimeDirectory=docbow
|
||||
Type=notify
|
||||
NotifyAccess=all
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,3 +0,0 @@
|
|||
README.rst
|
||||
help/fr/build-pfwb
|
||||
help/fr/build-pw
|
|
@ -1,5 +0,0 @@
|
|||
debian/conf/docbow.nginx /etc/nginx/sites-available/
|
||||
debian/conf/magic /usr/share/docbow
|
||||
debian/debian_config.py /usr/lib/docbow
|
||||
debian/docbow-manage /usr/bin
|
||||
debian/uwsgi.ini /etc/docbow
|
|
@ -1 +0,0 @@
|
|||
/usr/share/docbow/magic /etc/magic
|
|
@ -1,14 +0,0 @@
|
|||
/var/log/docbow/*.log {
|
||||
daily
|
||||
missingok
|
||||
rotate 365
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
create 640 root adm
|
||||
sharedscripts
|
||||
postrotate
|
||||
[ ! -f /var/run/docbow/docbow.pid ] || kill -HUP `cat /var/run/docbow/docbow.pid`
|
||||
endscript
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Postinst script for docbow
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
NAME=docbow
|
||||
USER=$NAME
|
||||
GROUP=$NAME
|
||||
HOME=/var/lib/$NAME
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
if ! getent group $GROUP > /dev/null 2>&1; then
|
||||
echo -n "Adding group $GROUP.." >&2
|
||||
addgroup --quiet --system $GROUP
|
||||
echo "..done" >&2
|
||||
fi
|
||||
if ! getent passwd $USER > /dev/null 2>&1; then
|
||||
echo -n "Adding user $USER.." >&2
|
||||
adduser --quiet --system --gecos "$NAME daemon" \
|
||||
--ingroup $GROUP \
|
||||
--no-create-home --home $HOME \
|
||||
$USER
|
||||
echo "..done" >&2
|
||||
fi
|
||||
if [ ! -f "/etc/$NAME/secret" ]; then
|
||||
echo -n "Generating Django secret.." >&2
|
||||
echo "export SECRET_KEY='`</dev/urandom tr -dc [:alnum:]-_\!\%\^:\; | head -c70`'" > /etc/$NAME/secret
|
||||
chmod 0640 /etc/$NAME/secret
|
||||
chown root:$GROUP /etc/$NAME/secret
|
||||
echo "..done" >&2
|
||||
fi
|
||||
|
||||
if [ ! -f "/etc/$NAME/db" ]; then
|
||||
cat > /etc/$NAME/db <<EOC
|
||||
export DATABASE_ENGINE='django.db.backends.postgresql_psycopg2'
|
||||
export DATABASE_NAME='docbow'
|
||||
export DATABASE_USER='docbow'
|
||||
export DATABASE_PASSWORD='SOME_PASSWORD'
|
||||
export DATABASE_HOST='localhost'
|
||||
EOC
|
||||
fi
|
||||
|
||||
chown $USER:$GROUP /var/lib/docbow \
|
||||
/var/lib/docbow/static \
|
||||
/var/lib/docbow/collectstatic \
|
||||
/var/lib/docbow/templates \
|
||||
/var/lib/docbow/media \
|
||||
/var/run/docbow \
|
||||
/var/log/docbow
|
||||
;;
|
||||
|
||||
reconfigure|abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
|
@ -1,24 +0,0 @@
|
|||
#!/bin/sh
|
||||
# postrm script for docbow
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
NAME=docbow
|
||||
USER=$NAME
|
||||
GROUP=$NAME
|
||||
|
||||
case "$1" in purge)
|
||||
deluser --quiet --system $NAME > /dev/null || true
|
||||
rm -rf /var/lib/$NAME/collectstatic
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$1" in remove|abort-install|purge)
|
||||
dpkg-divert --remove --package docbow --rename /etc/magic
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
#!/bin/sh
|
||||
# preinst script for docbow
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <new-preinst> `install'
|
||||
# * <new-preinst> `install' <old-version>
|
||||
# * <new-preinst> `upgrade' <old-version>
|
||||
# * <old-preinst> `abort-upgrade' <new-version>
|
||||
# for details, see https://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
|
||||
case "$1" in
|
||||
install|upgrade)
|
||||
dpkg-divert --add --package docbow --rename \
|
||||
--divert /etc/magic.libmagic \
|
||||
/etc/magic
|
||||
;;
|
||||
abort-upgrade)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "preinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
django_watson python3-django-watson
|
||||
django_journal python3-django-journal
|
||||
python_magic python3-magic
|
||||
typing
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
|
||||
export PYBUILD_NAME=docbow
|
||||
export PYBUILD_DISABLE=test
|
||||
|
||||
%:
|
||||
dh $@ --with python3 --buildsystem=pybuild
|
||||
|
||||
override_dh_install:
|
||||
dh_install
|
||||
mv $(CURDIR)/debian/docbow/usr/bin/manage.py $(CURDIR)/debian/docbow/usr/lib/docbow/manage.py
|
||||
|
||||
override_dh_auto_build:
|
||||
$(MAKE) -C help/fr
|
||||
dh_auto_build
|
|
@ -1 +0,0 @@
|
|||
3.0 (quilt)
|
|
@ -1,47 +0,0 @@
|
|||
[uwsgi]
|
||||
strict = true
|
||||
auto-procname = true
|
||||
procname-prefix-spaced = docbow
|
||||
|
||||
plugin = python3
|
||||
single-interpreter = true
|
||||
module = docbow_project.wsgi:application
|
||||
need-app = true
|
||||
vacuum = true
|
||||
|
||||
http-socket = /run/docbow/docbow.sock
|
||||
chmod-socket = 666
|
||||
vacuum = true
|
||||
|
||||
master = true
|
||||
enable-threads = true
|
||||
harakiri = 120
|
||||
|
||||
plugin = cheaper_busyness
|
||||
cheaper-algo = busyness
|
||||
processes = 500
|
||||
cheaper = 5
|
||||
cheaper-initial = 10
|
||||
cheaper-overload = 5
|
||||
cheaper-step = 10
|
||||
cheaper-busyness-multiplier = 30
|
||||
cheaper-busyness-min = 20
|
||||
cheaper-busyness-max = 70
|
||||
cheaper-busyness-backlog-alert = 16
|
||||
cheaper-busyness-backlog-step = 2
|
||||
|
||||
max-requests = 500
|
||||
max-worker-lifetime = 7200
|
||||
|
||||
buffer-size = 32768
|
||||
|
||||
py-tracebacker = /run/docbow/py-tracebacker.sock.
|
||||
stats = /run/docbow/stats.sock
|
||||
memory-report = true
|
||||
|
||||
ignore-sigpipe = true
|
||||
disable-write-exception = true
|
||||
|
||||
if-file = /etc/docbow/uwsgi-local.ini
|
||||
include = /etc/docbow/uwsgi-local.ini
|
||||
endif =
|
11
docbow-ctl
11
docbow-ctl
|
@ -1,11 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == '__main__':
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'docbow_project.settings')
|
||||
|
||||
execute_from_command_line(sys.argv)
|
|
@ -0,0 +1,29 @@
|
|||
import csv
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
def export_as_csv(modeladmin, request, queryset):
|
||||
"""
|
||||
Generic csv export admin action.
|
||||
"""
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied
|
||||
opts = modeladmin.model._meta
|
||||
response = HttpResponse(mimetype='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename=%s.csv' % unicode(opts).replace('.', '_')
|
||||
writer = csv.writer(response)
|
||||
field_names = [field.name for field in opts.fields]
|
||||
m2m_field_names = [m2m_field.name for m2m_field in opts.many_to_many]
|
||||
# Write a first row with header information
|
||||
writer.writerow(field_names+m2m_field_names)
|
||||
# Write data rows
|
||||
for obj in queryset:
|
||||
values = [ unicode(getattr(obj, field)) for field in field_names]
|
||||
for m2m_field in m2m_field_names:
|
||||
value = getattr(obj, m2m_field)
|
||||
value = u','.join(map(unicode, value.all()))
|
||||
values.append(unicode(value))
|
||||
writer.writerow(map(lambda x: unicode.encode(x, 'utf8'), values))
|
||||
return response
|
||||
export_as_csv.short_description = _("Export selected objects as csv file")
|
|
@ -0,0 +1,45 @@
|
|||
from django.contrib.auth.models import User
|
||||
|
||||
class DummyUser(object):
|
||||
def __init__(self, user, delegate):
|
||||
self.user = user
|
||||
self.delegate = delegate
|
||||
self.id = '%s,%s' % (user.id, delegate.id)
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return self.user.is_active
|
||||
|
||||
def save(self):
|
||||
self.delegate.last_login = self.last_login
|
||||
self.delegate.save()
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
|
||||
class DelegationAuthBackend:
|
||||
supports_object_permissions = False
|
||||
supports_anonymous_user = False
|
||||
|
||||
def authenticate(self, username=None, password=None):
|
||||
try:
|
||||
if '-' in username:
|
||||
prefix, suffix = username.rsplit('-', 1)
|
||||
delegate = User.objects.get(username=username)
|
||||
user = User.objects.get(username=prefix)
|
||||
if delegate.check_password(password):
|
||||
user.delegate = username
|
||||
return DummyUser(user, delegate)
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_user(self, id_pair):
|
||||
user_id, delegate_id = id_pair.rsplit(',', 1)
|
||||
try:
|
||||
user = User.objects.get(pk=user_id)
|
||||
delegate = User.objects.get(pk=delegate_id)
|
||||
user.delegate = delegate
|
||||
return user
|
||||
except User.DoesNotExist:
|
||||
return None
|
|
@ -0,0 +1,39 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
from django.contrib.auth import views as auth_views
|
||||
|
||||
from docbow.decorator import as_delegate
|
||||
import views
|
||||
import forms
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^login/$',
|
||||
auth_views.login,
|
||||
{'template_name': 'registration/login.html'},
|
||||
name='auth_login'),
|
||||
url(r'^logout/$',
|
||||
auth_views.logout,
|
||||
{'template_name': 'registration/logout.html'},
|
||||
name='auth_logout'),
|
||||
url(r'^password/change/$',
|
||||
as_delegate(auth_views.password_change),
|
||||
kwargs={'password_change_form':
|
||||
forms.PasswordChangeFormWithLogging},
|
||||
name='auth_password_change'),
|
||||
url(r'^password/change/done/$',
|
||||
auth_views.password_change_done,
|
||||
name='auth_password_change_done'),
|
||||
url(r'^password/reset/$',
|
||||
auth_views.password_reset,
|
||||
name='auth_password_reset',
|
||||
kwargs={'post_reset_redirect': '/accounts/password/reset/done/',
|
||||
'password_reset_form': forms.PasswordResetFormWithLogging }),
|
||||
url(r'^password/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
|
||||
auth_views.password_reset_confirm,
|
||||
name='auth_password_reset_confirm'),
|
||||
url(r'^password/reset/complete/$',
|
||||
auth_views.password_reset_complete,
|
||||
name='auth_password_reset_complete'),
|
||||
url(r'^password/reset/done/$',
|
||||
views.password_reset_done,
|
||||
name='auth_password_reset_done'),
|
||||
)
|
|
@ -1,48 +0,0 @@
|
|||
import csv
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponse
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
def export_as_csv(modeladmin, request, queryset):
|
||||
"""
|
||||
Generic csv export admin action.
|
||||
"""
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied
|
||||
opts = modeladmin.model._meta
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename=%s.csv' % force_str(opts).replace('.', '_')
|
||||
writer = csv.writer(response)
|
||||
field_names = [field.name for field in opts.fields]
|
||||
m2m_field_names = [m2m_field.name for m2m_field in opts.many_to_many]
|
||||
# Write a first row with header information
|
||||
writer.writerow(field_names + m2m_field_names)
|
||||
# Write data rows
|
||||
for obj in queryset:
|
||||
values = [force_str(getattr(obj, field)) for field in field_names]
|
||||
for m2m_field in m2m_field_names:
|
||||
value = getattr(obj, m2m_field)
|
||||
value = ','.join(map(force_str, value.all()))
|
||||
values.append(force_str(value))
|
||||
writer.writerow(map(lambda x: force_str(x), values))
|
||||
return response
|
||||
|
||||
|
||||
export_as_csv.short_description = _('Export selected objects as csv file')
|
||||
|
||||
|
||||
def activate_selected(modeladmin, request, queryset):
|
||||
queryset.update(is_active=True)
|
||||
|
||||
|
||||
activate_selected.short_description = _('Activate selected objects')
|
||||
|
||||
|
||||
def deactivate_selected(modeladmin, request, queryset):
|
||||
queryset.update(is_active=False)
|
||||
|
||||
|
||||
deactivate_selected.short_description = _('De-activate selected objects')
|
|
@ -1,330 +1,139 @@
|
|||
import functools
|
||||
import operator
|
||||
|
||||
import django.contrib.admin as admin
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth import admin as auth_admin
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
from django.urls import NoReverseMatch, re_path, reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
try:
|
||||
import thread
|
||||
except ImportError:
|
||||
import _thread as thread
|
||||
|
||||
import django_journal.admin
|
||||
|
||||
from docbow_project.docbow import actions, auth_views, forms, models, notification, views
|
||||
|
||||
TITLE = "Plate-forme sécurisée d'échange de documents"
|
||||
|
||||
|
||||
class GetSearchResultsMixin:
|
||||
def get_search_results(self, request, queryset, search_term):
|
||||
search_fields = self.get_search_fields(request)
|
||||
orm_lookups = [Q(**{f'{field}__icontains': search_term}) for field in search_fields]
|
||||
return queryset.filter(functools.reduce(operator.or_, orm_lookups)), False
|
||||
from django.conf.urls.defaults import patterns
|
||||
|
||||
import models
|
||||
import forms
|
||||
import views
|
||||
import signals
|
||||
from docbow_project.log import models as log_models, admin as log_admin
|
||||
import docbow_project.actions as actions
|
||||
|
||||
class DocbowAdminSite(admin.AdminSite):
|
||||
site_title = TITLE
|
||||
site_header = TITLE
|
||||
index_title = TITLE
|
||||
|
||||
pass
|
||||
|
||||
site = DocbowAdminSite('docbow_admin')
|
||||
site.disable_action('delete_selected')
|
||||
|
||||
|
||||
class DocumentAdmin(admin.ModelAdmin):
|
||||
list_display = ['date', 'sender', 'recipients', 'filetype', 'filename_links', 'comment', 'private']
|
||||
list_filter = ['sender', 'to_user', 'to_list', 'filetype', 'private']
|
||||
fields = ['date', 'sender', 'recipients', 'filetype', 'filename_links', 'comment', 'private']
|
||||
readonly_fields = fields
|
||||
filter_horizontal = ['to_user', 'to_list']
|
||||
list_display = [ 'date', 'sender', 'recipients', 'filetype', 'filename_links', 'comment' ]
|
||||
list_filter = [ 'sender', 'to_user', 'to_list', 'filetype' ]
|
||||
fields = [ 'date', 'sender', 'recipients', 'filetype', 'filename_links', 'comment']
|
||||
readonly_fields = [ 'sender', 'filetype', 'date', 'recipients', 'filename_links', 'comment' ]
|
||||
filter_horizontal = [ 'to_user', 'to_list' ]
|
||||
date_hierarchy = 'date'
|
||||
|
||||
|
||||
class SendingLimitationAdmin(admin.ModelAdmin):
|
||||
list_display = ['mailing_list', 'filetypes_list', 'lists_list']
|
||||
filter_horizontal = ['filetypes', 'lists']
|
||||
actions = [actions.export_as_csv, 'delete_selected']
|
||||
list_display = [ 'mailing_list', 'filetypes_list', 'lists_list' ]
|
||||
filter_horizontal = [ 'filetypes', 'lists' ]
|
||||
actions = [ actions.export_as_csv ]
|
||||
|
||||
def lists_list(self, obj):
|
||||
'''Display method for the field lists'''
|
||||
return ', '.join(obj.lists.values_list('name', flat=True))
|
||||
|
||||
lists_list.short_description = _('Limitation des destinataires')
|
||||
|
||||
def filetypes_list(self, obj):
|
||||
'''Display method for the field filetypes'''
|
||||
return ', '.join(obj.filetypes.values_list('name', flat=True))
|
||||
|
||||
filetypes_list.short_description = _('Limitation des types de fichier')
|
||||
|
||||
|
||||
class MailingListAdmin(GetSearchResultsMixin, admin.ModelAdmin):
|
||||
list_display = ['name', 'is_active']
|
||||
list_filter = ['is_active']
|
||||
search_fields = ['name', 'members__username', 'members__first_name', 'members__last_name']
|
||||
class MailingListAdmin(admin.ModelAdmin):
|
||||
list_display = [ 'name' ]
|
||||
search_fields = [ 'name', 'members__username', 'members__first_name',
|
||||
'members__last_name']
|
||||
ordering = ['name']
|
||||
form = forms.MailingListForm
|
||||
actions = [actions.export_as_csv]
|
||||
|
||||
def get_actions(self, request):
|
||||
"""Show delete actions only if user has delete rights
|
||||
Show activation actions only if user has rights to change mailing lists
|
||||
"""
|
||||
a = super().get_actions(request)
|
||||
if request.user.has_perm('docbow.delete_mailinglist'):
|
||||
a['delete_selected'] = self.get_action('delete_selected')
|
||||
if request.user.has_perm('docbow.change_mailinglist'):
|
||||
a['activate_selected'] = self.get_action(actions.activate_selected)
|
||||
a['deactivate_selected'] = self.get_action(actions.deactivate_selected)
|
||||
return a
|
||||
actions = [ actions.export_as_csv ]
|
||||
|
||||
class Media:
|
||||
js = ('docbow/js/SelectBox-count.js',)
|
||||
|
||||
class AttachedFileAdmin(admin.ModelAdmin):
|
||||
def get_model_perms(self, request):
|
||||
return {}
|
||||
|
||||
def get_urls(self):
|
||||
urls = super().get_urls()
|
||||
attached_file_urls = [re_path(r'^(.+)/download/$', self.download)]
|
||||
urls = super(AttachedFileAdmin, self).get_urls()
|
||||
attached_file_urls = patterns('',
|
||||
(r'^(.+)/download/$', self.download)
|
||||
)
|
||||
return attached_file_urls + urls
|
||||
|
||||
def download(self, request, object_id):
|
||||
'''Downlod view for attached files'''
|
||||
attached_file = models.AttachedFile.objects.get(pk=object_id)
|
||||
return views.upload(request, attached_file)
|
||||
|
||||
|
||||
class DocbowProfileInlineAdmin(admin.StackedInline):
|
||||
model = models.DocbowProfile
|
||||
extra = 0
|
||||
|
||||
|
||||
class DocbowUserAdmin(auth_admin.UserAdmin):
|
||||
fieldsets = (
|
||||
(None, {'fields': ('username', 'password')}),
|
||||
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
|
||||
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser')}),
|
||||
(_('Permissions'), {'fields': ('is_active', 'is_staff')}),
|
||||
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
|
||||
(_('Groups'), {'fields': ('groups',)}),
|
||||
)
|
||||
readonly_fields = ['last_login', 'date_joined']
|
||||
exclude = ['user_permissions']
|
||||
actions = [actions.export_as_csv]
|
||||
list_display = (
|
||||
'username',
|
||||
'email',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'delegations',
|
||||
'get_lists',
|
||||
'get_groups',
|
||||
'is_active',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
)
|
||||
inlines = [DocbowProfileInlineAdmin]
|
||||
|
||||
if 'mellon' in settings.INSTALLED_APPS:
|
||||
|
||||
class UserSAMLIdentifierInlineAdmin(admin.StackedInline):
|
||||
import mellon
|
||||
|
||||
model = mellon.models.UserSAMLIdentifier
|
||||
extra = 0
|
||||
|
||||
inlines += [UserSAMLIdentifierInlineAdmin]
|
||||
|
||||
def get_groups(self, user):
|
||||
return ', '.join(group.name for group in user.groups.all())
|
||||
|
||||
get_groups.short_description = _('groups')
|
||||
|
||||
def get_lists(self, user):
|
||||
return ', '.join(_list.name for _list in user.mailing_lists.all())
|
||||
|
||||
get_lists.short_description = _('mailing lists')
|
||||
|
||||
def get_actions(self, request):
|
||||
a = super().get_actions(request)
|
||||
if request.user.has_perm('auth.delete_docbowuser'):
|
||||
a['delete_selected'] = self.get_action('delete_selected')
|
||||
if request.user.has_perm('auth.change_docbowuser'):
|
||||
a['activate_selected'] = self.get_action(actions.activate_selected)
|
||||
a['deactivate_selected'] = self.get_action(actions.deactivate_selected)
|
||||
return a
|
||||
|
||||
def guest_account(self, instance):
|
||||
try:
|
||||
return instance.docbowprofile.is_guest
|
||||
except models.DocbowProfile.DoesNotExist:
|
||||
return False
|
||||
|
||||
guest_account.boolean = True
|
||||
guest_account.short_description = _('Guest account')
|
||||
|
||||
def delegations(self, instance):
|
||||
from_users = auth_models.User.objects.filter(delegations_to__to=instance)
|
||||
return models.list_to_csv(from_users, models.username)
|
||||
|
||||
delegations.short_description = _('Delegations by')
|
||||
|
||||
readonly_fields = [ 'last_login', 'date_joined' ]
|
||||
exclude = [ 'user_permissions' ]
|
||||
actions = [ actions.export_as_csv ]
|
||||
|
||||
class DocbowGroupAdmin(auth_admin.GroupAdmin):
|
||||
exclude = ['permissions']
|
||||
|
||||
exclude = [ 'permissions' ]
|
||||
|
||||
class MailboxAdmin(admin.ModelAdmin):
|
||||
list_display = ['owner', 'document', 'date']
|
||||
list_filter = ['owner', 'outbox']
|
||||
|
||||
list_display = [ 'owner', 'document', 'date', 'seen' ]
|
||||
list_filter = [ 'owner', 'outbox' ]
|
||||
def queryset(self, request):
|
||||
qs = super(MailboxAdmin, self).queryset(request)
|
||||
return qs
|
||||
def lookup_allowed(self, *args, **kwargs):
|
||||
'''Allow complex filters'''
|
||||
return True
|
||||
|
||||
|
||||
class InboxAdmin(MailboxAdmin):
|
||||
list_display = ['date', 'owner', 'document']
|
||||
fields = ['date', 'owner', 'document']
|
||||
readonly_fields = ['date', 'owner', 'document']
|
||||
|
||||
list_display = [ 'date', 'owner', 'document', 'seen', 'deleted' ]
|
||||
fields = [ 'date', 'owner', 'document', 'seen', 'deleted' ]
|
||||
readonly_fields = [ 'date', 'owner', 'document', 'seen' ]
|
||||
def queryset(self, request):
|
||||
'''Only show input mailboxes'''
|
||||
qs = super().queryset(request)
|
||||
qs = super(InboxAdmin, self).queryset(request)
|
||||
qs = qs.filter(outbox=False)
|
||||
return qs
|
||||
|
||||
|
||||
class OutboxAdmin(MailboxAdmin):
|
||||
list_display = ['date', 'owner', 'document']
|
||||
list_display = [ 'date', 'owner', 'document' ]
|
||||
fields = list_display
|
||||
readonly_fields = list_display
|
||||
|
||||
def queryset(self, request):
|
||||
'''Only show output mailboxes'''
|
||||
qs = super().queryset(request)
|
||||
qs = super(OutboxAdmin, self).queryset(request)
|
||||
qs = qs.filter(outbox=True)
|
||||
return qs
|
||||
|
||||
|
||||
class ContentAdmin(admin.ModelAdmin):
|
||||
verbose_name = _('Predefined content description')
|
||||
actions = [actions.export_as_csv, 'delete_selected']
|
||||
|
||||
actions = [ actions.export_as_csv ]
|
||||
|
||||
class AutomaticForwardingAdmin(admin.ModelAdmin):
|
||||
filter_horizontal = ['filetypes', 'originaly_to_user', 'forward_to_user', 'forward_to_list']
|
||||
filter_horizontal = [ 'filetypes', 'originaly_to_user', 'forward_to_user',
|
||||
'forward_to_list' ]
|
||||
form = forms.AutomaticForwardingForm
|
||||
actions = [actions.export_as_csv, 'delete_selected']
|
||||
|
||||
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
||||
if db_field.name in ('originaly_to_user', 'forward_to_user'):
|
||||
kwargs['queryset'] = models.non_guest_users()
|
||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
||||
|
||||
class FileTypeAttachedFileKindAdmin(admin.TabularInline):
|
||||
model = models.FileTypeAttachedFileKind
|
||||
sortable_field_name = 'position'
|
||||
extra = 0
|
||||
|
||||
actions = [ actions.export_as_csv ]
|
||||
|
||||
class FileTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'is_active']
|
||||
if settings.EXTRA_SENDERS:
|
||||
list_display.append('extra_senders')
|
||||
fields = list_display
|
||||
actions = [actions.export_as_csv]
|
||||
inlines = [FileTypeAttachedFileKindAdmin]
|
||||
|
||||
|
||||
class NotificationAdmin(GetSearchResultsMixin, admin.ModelAdmin):
|
||||
search_fields = [
|
||||
'user__username',
|
||||
'user__first_name',
|
||||
'user__last_name',
|
||||
'user__docbowprofile__mobile_phone',
|
||||
]
|
||||
list_display = ['create_dt', '_document', 'user', 'kind', 'done', 'failure']
|
||||
readonly_fields = ['ctx']
|
||||
date_hierarchy = 'create_dt'
|
||||
list_filter = ['user', 'kind', 'done']
|
||||
actions = ['retry', 'delete_selected']
|
||||
|
||||
def retry(self, request, queryset):
|
||||
queryset.update(done=False, failure=None)
|
||||
thread.start_new_thread(notification.process_notifications, ())
|
||||
|
||||
retry.short_description = _('Clear failure and done field, resubmitting ' 'the notifications.')
|
||||
|
||||
def object_link(self, obj):
|
||||
if obj is not None:
|
||||
url = '{}:{}_{}_change'.format(
|
||||
self.admin_site.name, obj.__class__._meta.app_label, obj.__class__._meta.model_name
|
||||
)
|
||||
try:
|
||||
url = reverse(url, args=(obj.id,))
|
||||
return f'<a href="{url}" class="external-link">{obj}</a>'
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
return ''
|
||||
|
||||
def _document(self, notification):
|
||||
return mark_safe(self.object_link(notification.document))
|
||||
|
||||
_document.short_description = _('Document')
|
||||
|
||||
|
||||
class JournalAdmin(django_journal.admin.JournalAdmin):
|
||||
def user(self, entry):
|
||||
'''Search and return any associated objectdata whose tag is "user"'''
|
||||
user, delegate = '', ''
|
||||
for objectdata in entry.objectdata_set.all():
|
||||
if objectdata.tag.name == 'user':
|
||||
user = self.object_filter_link(objectdata) + self.object_link(objectdata)
|
||||
if objectdata.tag.name == 'delegate':
|
||||
delegate = self.object_filter_link(objectdata) + self.object_link(objectdata)
|
||||
if user and delegate:
|
||||
return mark_safe(delegate + _(' as ') + user)
|
||||
elif user:
|
||||
return mark_safe(user)
|
||||
return mark_safe(_('None'))
|
||||
|
||||
user.short_description = _('User')
|
||||
|
||||
|
||||
class DelegationAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'by', 'to']
|
||||
|
||||
actions = [ actions.export_as_csv ]
|
||||
|
||||
# Docbow Admin Site
|
||||
site.register(auth_models.User, DocbowUserAdmin)
|
||||
site.register(models.DocbowUser, DocbowUserAdmin)
|
||||
site.register(models.DocbowGroup, DocbowGroupAdmin)
|
||||
|
||||
site.register(models.FileType, FileTypeAdmin)
|
||||
site.register(models.Content, ContentAdmin)
|
||||
site.register(models.Document, DocumentAdmin)
|
||||
site.register(models.MailingList, MailingListAdmin)
|
||||
site.register(models.Delegation, DelegationAdmin)
|
||||
site.register(models.Delegation)
|
||||
site.register(models.Inbox, InboxAdmin)
|
||||
site.register(models.Outbox, OutboxAdmin)
|
||||
site.register(log_models.LogLine, log_admin.LogLineAdmin)
|
||||
site.register(models.AttachedFile, AttachedFileAdmin)
|
||||
site.register(models.SendingLimitation, SendingLimitationAdmin)
|
||||
site.register(models.AutomaticForwarding, AutomaticForwardingAdmin)
|
||||
site.register(models.Notification, NotificationAdmin)
|
||||
_('Django_Journal')
|
||||
_('Auth')
|
||||
_('Docbow')
|
||||
site.register(django_journal.admin.Journal, JournalAdmin)
|
||||
|
||||
|
||||
# Superadmin Admin Site
|
||||
admin.site.register(models.FileType)
|
||||
|
@ -332,16 +141,5 @@ admin.site.register(models.Content, ContentAdmin)
|
|||
admin.site.register(models.Document)
|
||||
admin.site.register(models.AttachedFile)
|
||||
admin.site.register(models.MailingList, MailingListAdmin)
|
||||
admin.site.register(models.Delegation, DelegationAdmin)
|
||||
admin.site.register(models.Delegation)
|
||||
admin.site.register(models.Mailbox, MailboxAdmin)
|
||||
admin.site.register(models.Notification, NotificationAdmin)
|
||||
|
||||
|
||||
def login(request, *args, **kwargs):
|
||||
if request.user.is_authenticated and not (request.user.is_active and request.user.is_staff):
|
||||
raise PermissionDenied()
|
||||
return auth_views.login(request, *args, **kwargs)
|
||||
|
||||
|
||||
site.logout = auth_views.logout
|
||||
site.login = login
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
import sys
|
||||
|
||||
|
||||
class AppSettings:
|
||||
__DEFAULTS = {
|
||||
'PERSONAL_EMAIL': True,
|
||||
'MOBILE_PHONE': True,
|
||||
'GROUP_LISTING': True,
|
||||
'PRIVATE_DOCUMENTS': False,
|
||||
'EDIT_EMAIL': False,
|
||||
'DELEGATE_TO_EXISTING_USER': True,
|
||||
'DEFAULT_ACCEPT_NOTIFICATIONS_FOR_GUEST': True,
|
||||
}
|
||||
|
||||
def __init__(self, prefix):
|
||||
self.__prefix = prefix
|
||||
|
||||
@property
|
||||
def settings(self):
|
||||
if not hasattr(self, '_settings'):
|
||||
from django.conf import settings
|
||||
|
||||
self._settings = settings
|
||||
return self._settings
|
||||
|
||||
@property
|
||||
def DOCBOW_MAILBOX_PER_PAGE(self):
|
||||
return getattr(self.settings, 'DOCBOW_MAILBOX_PER_PAGE', 20)
|
||||
|
||||
@property
|
||||
def DOCBOW_MENU(self):
|
||||
from django.utils.translation import gettext_noop
|
||||
|
||||
return getattr(
|
||||
self.settings,
|
||||
'DOCBOW_MENU',
|
||||
[
|
||||
('send-file-selector', gettext_noop('send-file_menu')),
|
||||
('inbox', gettext_noop('inbox_menu')),
|
||||
('outbox', gettext_noop('outbox_menu')),
|
||||
('docbow_admin:index', gettext_noop('admin_menu')),
|
||||
('profile', gettext_noop('profile_menu')),
|
||||
('mailing-lists', gettext_noop('mailing-lists')),
|
||||
('help', gettext_noop('help_menu')),
|
||||
('contact', gettext_noop('contact_menu')),
|
||||
],
|
||||
)
|
||||
|
||||
@property
|
||||
def BASE_URL(self):
|
||||
return getattr(self.settings, 'DOCBOW_BASE_URL', 'http://localhost:8000')
|
||||
|
||||
@property
|
||||
def TRUNCATE_FILENAME(self):
|
||||
return getattr(self.settings, 'DOCBOW_TRUNCATE_FILENAME', 80)
|
||||
|
||||
@property
|
||||
def MAX_FILE_SIZE(self):
|
||||
return getattr(self.settings, 'DOCBOW_MAX_FILE_SIZE', 10 * 1024 * 1024)
|
||||
|
||||
@property
|
||||
def MIME_BUFFER_SIZE(self):
|
||||
return getattr(self.settings, 'DOCBOW_MIME_BUFFER_SIZE', 300000)
|
||||
|
||||
def __getattr__(self, name):
|
||||
from django.conf import settings
|
||||
|
||||
if name not in self.__DEFAULTS:
|
||||
raise AttributeError
|
||||
return getattr(settings, self.__prefix + name, self.__DEFAULTS[name])
|
||||
|
||||
|
||||
app_settings = AppSettings(prefix='DOCBOW_')
|
||||
app_settings.__name__ = __name__
|
||||
sys.modules[__name__] = app_settings
|
|
@ -1,8 +0,0 @@
|
|||
import django.apps
|
||||
|
||||
|
||||
class AppConfig(django.apps.AppConfig):
|
||||
name = 'docbow_project.docbow'
|
||||
|
||||
def ready(self):
|
||||
from . import signals # noqa: F401
|
|
@ -1,52 +0,0 @@
|
|||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
def set_auth_hash_getter(user, delegate):
|
||||
if hasattr(user, 'get_session_auth_hash') and hasattr(delegate, 'get_session_auth_hash'):
|
||||
user.get_session_auth_hash = delegate.get_session_auth_hash
|
||||
|
||||
|
||||
class DelegationAuthBackend:
|
||||
supports_object_permissions = False
|
||||
supports_anonymous_user = False
|
||||
|
||||
def authenticate(self, request, username=None, password=None):
|
||||
try:
|
||||
if '-' in username:
|
||||
prefix, suffix = username.rsplit('-', 1)
|
||||
delegate = User.objects.get(username=username, docbowprofile__is_guest=True)
|
||||
if delegate.check_password(password):
|
||||
return delegate
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_user(self, user_id):
|
||||
try:
|
||||
delegate = User.objects.get(pk=user_id, docbowprofile__is_guest=True)
|
||||
user = User.objects.get(username=delegate.username.rsplit('-', 1)[0])
|
||||
user.delegate = delegate
|
||||
set_auth_hash_getter(user, delegate)
|
||||
return user
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
try:
|
||||
import mellon.backends
|
||||
|
||||
class DocbowMellonAuthBackend(mellon.backends.SAMLBackend):
|
||||
def get_user(self, user_id):
|
||||
try:
|
||||
delegate = User.objects.get(pk=user_id, docbowprofile__is_guest=True)
|
||||
if delegate.delegations_by.count() != 1:
|
||||
return None
|
||||
user = delegate.delegations_by.first().by
|
||||
user.delegate = delegate
|
||||
set_auth_hash_getter(user, delegate)
|
||||
return user
|
||||
except User.DoesNotExist:
|
||||
return super().get_user(user_id)
|
||||
|
||||
except ImportError:
|
||||
pass
|
|
@ -1,39 +0,0 @@
|
|||
from django.contrib.auth import views as auth_views
|
||||
from django.urls import path, re_path, reverse_lazy
|
||||
|
||||
from docbow_project.docbow import auth_views as docbow_auth_views
|
||||
from docbow_project.docbow import forms, views
|
||||
|
||||
urlpatterns = [
|
||||
path('login/', docbow_auth_views.login, {'template_name': 'registration/login.html'}, name='auth_login'),
|
||||
path(
|
||||
'logout/',
|
||||
auth_views.LogoutView.as_view(template_name='registration/logout.html'),
|
||||
name='auth_logout',
|
||||
),
|
||||
path('password/change/', views.password_change, name='auth_password_change'),
|
||||
path(
|
||||
'password/change/done/',
|
||||
auth_views.PasswordChangeDoneView.as_view(),
|
||||
name='auth_password_change_done',
|
||||
),
|
||||
path(
|
||||
'password/reset/',
|
||||
auth_views.PasswordResetView.as_view(
|
||||
success_url=reverse_lazy('auth_password_reset_done'),
|
||||
form_class=forms.PasswordResetFormWithLogging,
|
||||
),
|
||||
name='auth_password_reset',
|
||||
),
|
||||
re_path(
|
||||
r'^password/reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
|
||||
auth_views.PasswordResetConfirmView.as_view(),
|
||||
name='auth_password_reset_confirm',
|
||||
),
|
||||
path(
|
||||
'password/reset/complete/',
|
||||
auth_views.PasswordResetCompleteView.as_view(),
|
||||
name='password_reset_complete',
|
||||
),
|
||||
path('password/reset/done/', views.password_reset_done, name='auth_password_reset_done'),
|
||||
]
|
|
@ -1,29 +0,0 @@
|
|||
import urllib.parse
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import resolve_url
|
||||
|
||||
if 'mellon' in settings.INSTALLED_APPS:
|
||||
from mellon.utils import get_idps
|
||||
else:
|
||||
|
||||
def get_idps():
|
||||
return []
|
||||
|
||||
|
||||
def login(request, *args, **kwargs):
|
||||
if any(get_idps()):
|
||||
if 'next' not in request.GET:
|
||||
return HttpResponseRedirect(resolve_url('mellon_login'))
|
||||
return HttpResponseRedirect(
|
||||
resolve_url('mellon_login') + '?next=' + urllib.parse.quote(request.GET.get('next'))
|
||||
)
|
||||
return auth_views.LoginView.as_view(*args, **kwargs)(request)
|
||||
|
||||
|
||||
def logout(request, *args, **kwargs):
|
||||
if any(get_idps()):
|
||||
return HttpResponseRedirect(resolve_url('mellon_logout'))
|
||||
return auth_views.LogoutView.as_view(*args, **kwargs)(request)
|
|
@ -1,27 +0,0 @@
|
|||
class FormWithRequestMixin:
|
||||
def get_form_kwargs(self, **kwargs):
|
||||
kwargs = super().get_form_kwargs(**kwargs)
|
||||
kwargs['request'] = self.request
|
||||
return kwargs
|
||||
|
||||
|
||||
class FormWithPrefixMixin:
|
||||
# deprecated after Django 1.6
|
||||
prefix = None
|
||||
|
||||
def get_form_kwargs(self, **kwargs):
|
||||
kwargs = super().get_form_kwargs(**kwargs)
|
||||
kwargs['prefix'] = self.prefix
|
||||
return kwargs
|
||||
|
||||
|
||||
class FormWithPostTarget(FormWithPrefixMixin):
|
||||
def get_form_kwargs(self, **kwargs):
|
||||
kwargs = super().get_form_kwargs(**kwargs)
|
||||
if not self.is_post_target():
|
||||
kwargs.pop('data', None)
|
||||
kwargs.pop('files', None)
|
||||
return kwargs
|
||||
|
||||
def is_post_target(self):
|
||||
return self.prefix + '-validate' in self.request.POST
|
|
@ -1,11 +0,0 @@
|
|||
from django.conf import settings
|
||||
from django.shortcuts import resolve_url
|
||||
|
||||
|
||||
def settings_url_processor(request):
|
||||
logout_url = settings.LOGOUT_URL
|
||||
logout_url = resolve_url(logout_url)
|
||||
return {
|
||||
'logout_url': logout_url,
|
||||
'portal_base_url': getattr(settings, 'PORTAL_BASE_URL', None),
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
try:
|
||||
from functools import wraps
|
||||
except ImportError:
|
||||
from django.utils.functional import wraps # Python 2.4 fallback.
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.decorators import available_attrs
|
||||
|
||||
def no_delegate(view_func):
|
||||
'''
|
||||
Forbid delegated account to use this view.
|
||||
'''
|
||||
@wraps(view_func, assigned=available_attrs(view_func))
|
||||
def f(request, *args, **kwargs):
|
||||
if hasattr(request.user, 'delegate'):
|
||||
messages.warning(request, _('Your delegation does not allow you to do this action'))
|
||||
return redirect('inbox')
|
||||
return view_func(request, *args, **kwargs)
|
||||
return f
|
||||
|
||||
def as_delegate(view_func):
|
||||
'''
|
||||
Replace the effective user by the real user of the delegate for the
|
||||
given view.
|
||||
'''
|
||||
@wraps(view_func, assigned=available_attrs(view_func))
|
||||
def f(request, *args, **kwargs):
|
||||
if hasattr(request.user, 'delegate'):
|
||||
old_user = request.user
|
||||
request.user = request.user.delegate
|
||||
out = view_func(request, *args, **kwargs)
|
||||
request.user = old_user
|
||||
return out
|
||||
else:
|
||||
return view_func(request, *args, **kwargs)
|
||||
return f
|
|
@ -1,57 +0,0 @@
|
|||
from functools import wraps
|
||||
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.cache import patch_cache_control
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.decorators.cache import never_cache as old_never_cache
|
||||
|
||||
|
||||
def no_delegate(view_func):
|
||||
"""
|
||||
Forbid delegated account to use this view.
|
||||
"""
|
||||
|
||||
@wraps(view_func)
|
||||
def f(request, *args, **kwargs):
|
||||
if hasattr(request.user, 'delegate'):
|
||||
messages.warning(request, _('Your delegation does not allow you to do this action'))
|
||||
return redirect('inbox')
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
return f
|
||||
|
||||
|
||||
def as_delegate(view_func):
|
||||
"""
|
||||
Replace the effective user by the real user of the delegate for the
|
||||
given view.
|
||||
"""
|
||||
|
||||
@wraps(view_func)
|
||||
def f(request, *args, **kwargs):
|
||||
if hasattr(request.user, 'delegate'):
|
||||
old_user = request.user
|
||||
request.user = request.user.delegate
|
||||
out = view_func(request, *args, **kwargs)
|
||||
request.user = old_user
|
||||
return out
|
||||
else:
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
return f
|
||||
|
||||
|
||||
def never_cache(view_func):
|
||||
'''Block client caching in all browsers.'''
|
||||
view_func = old_never_cache(view_func)
|
||||
|
||||
@wraps(view_func)
|
||||
def f(request, *args, **kwargs):
|
||||
result = view_func(request, *args, **kwargs)
|
||||
patch_cache_control(result, no_cache=True)
|
||||
patch_cache_control(result, no_store=True)
|
||||
patch_cache_control(result, must_revalidate=True)
|
||||
return result
|
||||
|
||||
return f
|
Binary file not shown.
Binary file not shown.
|
@ -1,57 +1,42 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.forms import MultipleChoiceField, ValidationError
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from docbow_project.docbow import pyuca
|
||||
from docbow_project.docbow.models import MailingList, username
|
||||
from docbow_project.docbow.widgets import FilteredSelectMultiple, ForcedValueWidget
|
||||
from django.forms import ValidationError, MultipleChoiceField
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
import pyuca
|
||||
from models import username, MailingList
|
||||
from widgets import ForcedValueWidget, FilteredSelectMultiple
|
||||
|
||||
def order_choices(choices):
|
||||
'''Sort choices using Unicode collations'''
|
||||
return sorted(list(choices), key=lambda x: pyuca.collator.sort_key(x[1]))
|
||||
|
||||
return sorted(list(choices),
|
||||
key=lambda x: pyuca.collator.sort_key(x[1]))
|
||||
|
||||
def order_field_choices(field):
|
||||
'''Order choices of this field'''
|
||||
choices = list(field.choices)
|
||||
|
||||
field.choices = [choice for choice in choices if choice[1].startswith('---')] + order_choices(
|
||||
[choice for choice in field.choices if not choice[1].startswith('---')]
|
||||
)
|
||||
print(field.choices)
|
||||
|
||||
|
||||
class Func2Iter:
|
||||
'''Transform a generator producing function into an iterator'''
|
||||
field.choices = order_choices(field.choices)
|
||||
|
||||
class Func2Iter(object):
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __iter__(self):
|
||||
return self.func().__iter__()
|
||||
|
||||
|
||||
class RecipientField(MultipleChoiceField):
|
||||
'''Field allowing selection among user or list for recipients'''
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
self.user_qs = kwargs.pop('user_qs', User.objects.filter())
|
||||
self.user_qs = self.user_qs.filter(is_active=True, delegations_by__isnull=True)
|
||||
self.list_qs = kwargs.pop('list_qs', MailingList.objects.active())
|
||||
super().__init__(*args, **kwargs)
|
||||
self._choices = self.widget.choices = Func2Iter(self.get_recipients_choices)
|
||||
self.list_qs = kwargs.pop('list_qs', MailingList.objects.all())
|
||||
kwargs.setdefault('choices', Func2Iter(self.get_recipients_choices))
|
||||
super(RecipientField, self).__init__(*args, **kwargs)
|
||||
|
||||
def reset_choices(self):
|
||||
'''Reset the list of choices'''
|
||||
self._set_choices(Func2Iter(self.get_recipients_choices))
|
||||
|
||||
def get_recipients_choices(self):
|
||||
"""
|
||||
Create a unique list of recipients from the list of users and the
|
||||
list of mailing-lists.
|
||||
"""
|
||||
'''
|
||||
Create a unique list of recipients from the list of users and the
|
||||
list of mailing-lists.
|
||||
'''
|
||||
users = self.user_qs
|
||||
if self.user:
|
||||
users = users.exclude(pk=self.user.pk)
|
||||
|
@ -68,18 +53,20 @@ class RecipientField(MultipleChoiceField):
|
|||
list_choices.append((i, mailing_list.name))
|
||||
list_choices = order_choices(list_choices)
|
||||
if list_choices and user_choices:
|
||||
choices = list_choices + [('', '---')] + user_choices
|
||||
choices = list_choices+[('', '---')]+user_choices
|
||||
else:
|
||||
choices = list_choices + user_choices
|
||||
choices = list_choices+user_choices
|
||||
if len(choices) == 1:
|
||||
self.widget = ForcedValueWidget(value=[choices[0][0]], display_value=choices[0][1])
|
||||
self.widget = ForcedValueWidget(value=[choices[0][0]],
|
||||
display_value=choices[0][1])
|
||||
else:
|
||||
self.widget = FilteredSelectMultiple(_('Recipients'), False)
|
||||
return choices
|
||||
|
||||
def clean(self, value):
|
||||
'''Validate that the value is not empty.'''
|
||||
value = super().clean(value)
|
||||
value = super(RecipientField, self).clean(value)
|
||||
if not value:
|
||||
raise ValidationError(_('you must set at least one user recipient or one list recipient...'))
|
||||
raise ValidationError(_(u'you must set at least one user recipient or one list recipient...'))
|
||||
return value
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
[
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Envoi en Commission" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Envoi de document(s)" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Texte(s) adopté(s) en séance plénière" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Transmission des IQO" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Décision de la Conférence des présidents" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Liste" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Objet et motivation - questions d'actualité" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Addendum" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Corrigendum" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Erratum" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Avis" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Budget" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Ajustement" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Préfiguration" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Budget de fonctionnement" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Cahier d'observations de la Cour des comptes" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Règlement définitif" } },
|
||||
{ "pk": null, "model": "docbow.Content", "fields": { "description": "Décret-programme" } }
|
||||
]
|
|
@ -0,0 +1,28 @@
|
|||
[
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "BT"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Budget"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Circulaire"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "CRA"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "CRI/CRIC"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Correction CRI/CRIC"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Décret-Projet"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Décret-Proposition"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "IQO"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "ODJC"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "ODJS"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "QA"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "QE-question"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "QE-réponse"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Résolution"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Notification d'une décision du Gouvernement"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "ODJ du Gouvernement"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Accord"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Arrêt CC"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Motion"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Rapport hors projet/proposition"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Documents européens"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Documents préparatoires"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Arrêtés ministériels"}},
|
||||
{ "pk": null, "model": "docbow.filetype", "fields": {"name": "Divers"}}
|
||||
|
||||
]
|
|
@ -66,7 +66,7 @@
|
|||
]
|
||||
},
|
||||
"model": "auth.group",
|
||||
"pk": ["Administrateurs des listes"]
|
||||
"pk": null
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
|
@ -182,6 +182,11 @@
|
|||
"docbow",
|
||||
"sendinglimitation"
|
||||
],
|
||||
[
|
||||
"change_logline",
|
||||
"auth",
|
||||
"logline"
|
||||
],
|
||||
[
|
||||
"add_sendinglimitation",
|
||||
"docbow",
|
||||
|
@ -200,7 +205,7 @@
|
|||
]
|
||||
},
|
||||
"model": "auth.group",
|
||||
"pk": ["Administrateurs"]
|
||||
"pk": null
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
|
@ -208,6 +213,6 @@
|
|||
"permissions": []
|
||||
},
|
||||
"model": "auth.group",
|
||||
"pk": ["Contact \u00ab Administrateur du syst\u00e8me \u00bb"]
|
||||
"pk": null
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,717 +1,194 @@
|
|||
import collections
|
||||
import datetime
|
||||
import hashlib
|
||||
import hmac
|
||||
import logging
|
||||
import os.path
|
||||
import unicodedata
|
||||
import urllib.parse
|
||||
|
||||
from django.forms import ModelForm, Form, \
|
||||
ModelMultipleChoiceField, Textarea, EmailField, \
|
||||
CharField
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple as AdminFilteredSelectMultiple
|
||||
from django.contrib.auth.forms import PasswordChangeForm, PasswordResetForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.db.models.query import Q
|
||||
from django.forms import CharField, EmailField, Form, ModelChoiceField, ModelForm, Textarea, ValidationError
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
from django.utils.translation import gettext as _
|
||||
from django_journal import journal as django_journal
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple as AdminFilteredSelectMultiple
|
||||
from django.forms import ValidationError
|
||||
|
||||
from docbow_project.docbow import app_settings, fields, models, notification, pyuca, widgets
|
||||
from docbow_project.docbow.fields import RecipientField
|
||||
from docbow_project.docbow.middleware import get_extra
|
||||
from docbow_project.docbow.models import (
|
||||
AttachedFile,
|
||||
AutomaticForwarding,
|
||||
Content,
|
||||
DocbowProfile,
|
||||
Document,
|
||||
FileTypeAttachedFileKind,
|
||||
MailingList,
|
||||
is_guest,
|
||||
non_guest_users,
|
||||
username,
|
||||
)
|
||||
from docbow_project.docbow.utils import a2_wscall, mime_types_to_extensions, truncate_filename
|
||||
from docbow_project.docbow.validators import phone_normalize, validate_fr_be_phone
|
||||
from docbow_project.docbow.widgets import (
|
||||
FilteredSelectMultiple,
|
||||
JqueryFileUploadInput,
|
||||
TextInpuWithPredefinedValues,
|
||||
)
|
||||
from models import Document, username, MailingList, FileType, Content, \
|
||||
AttachedFile, AutomaticForwarding
|
||||
from uni_form.helpers import FormHelper, Submit, Layout, ButtonHolder
|
||||
import pyuca
|
||||
from widgets import TextInpuWithPredefinedValues, JqueryFileUploadInput
|
||||
from fields import RecipientField
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
class UserChoiceField(ModelMultipleChoiceField):
|
||||
def label_from_instance(self, obj):
|
||||
return username(obj)
|
||||
|
||||
def order_choices(choices):
|
||||
return sorted(list(choices),
|
||||
key=lambda x: pyuca.collator.sort_key(x[1]))
|
||||
|
||||
class RecipientForm:
|
||||
"""
|
||||
Base form mixin for forms containing a RecipienField, i.e. all
|
||||
forms for sending documents.
|
||||
"""
|
||||
def order_field_choices(field):
|
||||
field.choices = order_choices(field.choices)
|
||||
|
||||
class RecipientForm(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop('user', None)
|
||||
self.user = user
|
||||
user_qs = kwargs.pop('user_qs', None)
|
||||
if user_qs is None:
|
||||
user_qs = non_guest_users()
|
||||
list_qs = kwargs.pop('list_qs', MailingList.objects.active())
|
||||
super().__init__(*args, **kwargs)
|
||||
user_qs = kwargs.pop('user_qs', User.objects.filter())
|
||||
user_qs = user_qs.filter(is_active=True, delegations_by__isnull=True)
|
||||
list_qs = kwargs.pop('list_qs', MailingList.objects.all())
|
||||
super(RecipientForm, self).__init__(*args, **kwargs)
|
||||
self.fields['recipients'].user = user
|
||||
self.fields['recipients'].user_qs = user_qs
|
||||
self.fields['recipients'].list_qs = list_qs
|
||||
self.fields['recipients'].reset_choices()
|
||||
|
||||
|
||||
class ForwardingForm(RecipientForm, Form):
|
||||
'''Form for forwarding documents'''
|
||||
|
||||
recipients = RecipientField(label=_('Recipients'), required=True)
|
||||
sender = ModelChoiceField(label=_('Sender'), queryset=User.objects.all())
|
||||
|
||||
class Media:
|
||||
js = ('js/askdirtyform.js',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
assert self.user
|
||||
self.default_sender = self.user
|
||||
if is_guest(self.default_sender):
|
||||
self.default_sender = self.user.delegations_by.all()[0].by
|
||||
delegations = User.objects.none()
|
||||
else:
|
||||
self.default_sender = self.user
|
||||
delegations = (
|
||||
non_guest_users()
|
||||
.filter(Q(id=self.user.id) | Q(delegations_to__to=self.user))
|
||||
.order_by('last_name', 'first_name', 'username')
|
||||
.distinct()
|
||||
recipients = RecipientField(label=_('Recipients'))
|
||||
layout = Layout(
|
||||
'recipients',
|
||||
ButtonHolder(
|
||||
Submit('forward', _('forward the document'))),
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
if len(delegations) > 1:
|
||||
self.fields['sender'].queryset = delegations
|
||||
self.fields['sender'].label_from_instance = lambda y: username(y)
|
||||
else:
|
||||
del self.fields['sender']
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
if not cleaned_data.get('sender'):
|
||||
cleaned_data['sender'] = self.default_sender
|
||||
return cleaned_data
|
||||
|
||||
helper = FormHelper()
|
||||
helper.form_method = 'POST'
|
||||
helper.add_layout(layout)
|
||||
helper.form_action = ''
|
||||
|
||||
def max_filename_length():
|
||||
"""Compute the maximum filename length from the possible maximum length of
|
||||
the AttachedFile model."""
|
||||
field = AttachedFile._meta.get_field('content')
|
||||
field = AttachedFile._meta.get_field_by_name('content')[0]
|
||||
prefix = field.generate_filename(None, '')
|
||||
max_length = field.max_length
|
||||
return max_length - len(prefix)
|
||||
|
||||
|
||||
class FileForm(RecipientForm, ModelForm):
|
||||
'''Form for creating a new mailing'''
|
||||
|
||||
user = None
|
||||
recipients = RecipientField(label=_('Recipients'))
|
||||
layout = Layout(
|
||||
'filetype',
|
||||
'content',
|
||||
'recipients',
|
||||
'comment',
|
||||
ButtonHolder(
|
||||
Submit('send', _('send a file'))),
|
||||
)
|
||||
content = forms.Field(label=_('Attached files'),
|
||||
widget=JqueryFileUploadInput(max_filename_length=max_filename_length()))
|
||||
|
||||
helper = FormHelper()
|
||||
helper.form_method = 'POST'
|
||||
helper.add_layout(layout)
|
||||
helper.form_action = ''
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.filetype_qs = kwargs.pop('filetype_qs', FileType.objects.all())
|
||||
super(FileForm, self).__init__(*args, **kwargs)
|
||||
old_widget = self.fields['comment'].widget
|
||||
self.fields['comment'].widget = TextInpuWithPredefinedValues(attrs=old_widget.attrs,
|
||||
choices=self.get_predefined_comments())
|
||||
self.fields['filetype'].empty_label = '---'
|
||||
self.fields['filetype'].queryset = self.filetype_qs
|
||||
order_field_choices(self.fields['filetype'])
|
||||
|
||||
def get_predefined_comments(self):
|
||||
choices = [ (content.description,)*2 for content in Content.objects.all() ]
|
||||
choices.insert(0, ('---','---'))
|
||||
return choices
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
exclude = ('filetype', 'date', 'to_user', 'to_list', '_timestamp', 'real_sender', 'reply_to')
|
||||
widgets = {'extra_senders': FilteredSelectMultiple(_('Extra Senders'), False)}
|
||||
exclude = ('sender','date', 'to_user', 'to_list')
|
||||
|
||||
class Media:
|
||||
css = {'all': ('docbow/css/send-file.css', 'docbow/css/send_file_form.css')}
|
||||
js = ('js/askdirtyform.js', 'js/url-preload.js', 'js/foldable.js')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
'''Initialize the form.'''
|
||||
self.file_type = kwargs.pop('file_type')
|
||||
self.attached_file_kinds = self.file_type.filetypeattachedfilekind_set.all()
|
||||
self.reply_to = kwargs.pop('reply_to', None)
|
||||
self.default_sender = kwargs.pop('default_sender', None)
|
||||
self.delegations = kwargs.pop('delegations', [])
|
||||
initial = kwargs.setdefault('initial', {})
|
||||
if self.reply_to:
|
||||
doc = self.reply_to
|
||||
initial['sender'] = kwargs.get('user', None)
|
||||
initial['recipients'] = ['user-%s' % doc.sender.id]
|
||||
if doc.extra_senders.exists():
|
||||
initial['recipients'] += ['user-%s' % sender.pk for sender in doc.extra_senders.all()]
|
||||
initial['comment'] = 'Re: ' + doc.comment
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
self.content_fields = []
|
||||
if self.attached_file_kinds:
|
||||
insert_index = 2
|
||||
for attached_file_kind in self.attached_file_kinds:
|
||||
key = 'content-%s' % attached_file_kind.id
|
||||
self.content_fields.append((key, attached_file_kind))
|
||||
label = attached_file_kind.name
|
||||
mime_types = attached_file_kind.get_mime_types()
|
||||
widget = JqueryFileUploadInput(
|
||||
max_filename_length=max_filename_length(),
|
||||
extensions=mime_types_to_extensions(mime_types),
|
||||
attached_file_kind=attached_file_kind,
|
||||
)
|
||||
self.fields[key] = forms.Field(label=label, widget=widget)
|
||||
insert_index += 1
|
||||
else:
|
||||
attached_file_kind = FileTypeAttachedFileKind(mime_types='*/*')
|
||||
self.content_fields = [('content', attached_file_kind)]
|
||||
widget = JqueryFileUploadInput(
|
||||
max_filename_length=max_filename_length(), attached_file_kind=attached_file_kind
|
||||
)
|
||||
self.fields['content'] = forms.Field(label=_('Attached files'), widget=widget)
|
||||
|
||||
old_widget = self.fields['comment'].widget
|
||||
self.fields['comment'].widget = TextInpuWithPredefinedValues(
|
||||
attrs=old_widget.attrs, choices=self.get_predefined_comments()
|
||||
)
|
||||
if len(self.delegations) > 1:
|
||||
self.fields['sender'].queryset = self.delegations
|
||||
self.fields['sender'].label_from_instance = lambda y: username(y)
|
||||
fields.order_field_choices(self.fields['sender'])
|
||||
else:
|
||||
del self.fields['sender']
|
||||
|
||||
self.fields['private'].widget.attrs['class'] = 'checkboxinput'
|
||||
if not app_settings.PRIVATE_DOCUMENTS:
|
||||
del self.fields['private']
|
||||
|
||||
if self.reply_to or not settings.EXTRA_SENDERS or not self.file_type.extra_senders:
|
||||
del self.fields['extra_senders']
|
||||
else:
|
||||
self.fields['extra_senders'].required = False
|
||||
extra_senders_qs = User.objects.filter(is_active=True).exclude(docbowprofile__is_guest=True)
|
||||
if self.user:
|
||||
extra_senders_qs = extra_senders_qs.exclude(pk=self.user.pk)
|
||||
extra_senders = [(user.pk, username(user)) for user in extra_senders_qs]
|
||||
extra_senders = sorted(list(extra_senders), key=lambda x: pyuca.collator.sort_key(x[1]))
|
||||
self.fields['extra_senders'].choices = extra_senders
|
||||
|
||||
def template_content_fields(self):
|
||||
return [self[name] for name, _ in self.content_fields]
|
||||
|
||||
def get_predefined_comments(self):
|
||||
"""Return a list of predefined comments, structured as choice list for
|
||||
a form field.
|
||||
"""
|
||||
choices = [(content.description,) * 2 for content in Content.objects.all()]
|
||||
choices.insert(0, ('---', '---'))
|
||||
return choices
|
||||
css = { 'all': ('css/send-file.css', 'docbow/css/send_file_form.css'
|
||||
)}
|
||||
|
||||
def clean(self):
|
||||
'''Validate that there is at least one file attached to this mailing.'''
|
||||
cleaned_data = super().clean()
|
||||
for field, attached_file_kind in self.content_fields:
|
||||
upload_id, upload_files = self.cleaned_data.get(field, (None, []))
|
||||
max_files = attached_file_kind.cardinality
|
||||
min_files = attached_file_kind.minimum
|
||||
errors = []
|
||||
if max_files and len(upload_files) > max_files:
|
||||
errors.append(_('You must attach at most %d file(s).') % max_files)
|
||||
if min_files and len(upload_files) < min_files:
|
||||
errors.append(_('You must attach at least %d file(s).') % min_files)
|
||||
for upload_file in upload_files:
|
||||
if not attached_file_kind.match_file(upload_file):
|
||||
mime_types = attached_file_kind.get_mime_types()
|
||||
file_name = os.path.basename(upload_file.name)
|
||||
msg = _('The file name "{file_name}" does not match the patterns "{extensions}".').format(
|
||||
file_name=file_name, extensions=mime_types_to_extensions(mime_types)
|
||||
)
|
||||
errors.append(msg)
|
||||
if errors:
|
||||
self._errors[field] = self.error_class(errors)
|
||||
|
||||
if 'extra_senders' in cleaned_data:
|
||||
if len(cleaned_data['extra_senders']) > self.file_type.extra_senders:
|
||||
self._errors['extra_senders'] = self.error_class(
|
||||
[_('No more than %s additional senders allowed') % self.file_type.extra_senders]
|
||||
)
|
||||
|
||||
cleaned_data = super(FileForm, self).clean()
|
||||
upload_id, files = self.cleaned_data.get('content', (None, []))
|
||||
if not files:
|
||||
self._errors['content'] = self.error_class([_(u'You must attach at least one file.')])
|
||||
return cleaned_data
|
||||
|
||||
def save(self, commit=False):
|
||||
self.instance.filetype = self.file_type
|
||||
if not self.instance.sender_id:
|
||||
assert self.default_sender
|
||||
self.instance.sender = self.default_sender
|
||||
if self.reply_to:
|
||||
self.instance.reply_to = self.reply_to
|
||||
if self.user != self.instance.sender:
|
||||
self.instance.real_sender = username(self.user)
|
||||
return super().save(commit=commit)
|
||||
|
||||
def save_attachments(self):
|
||||
"""Create a new AttachedFile object for each uploaded file; and attach
|
||||
them to the newly created Document object."""
|
||||
instance = self.instance
|
||||
for field, attached_file_kind in self.content_fields:
|
||||
upload_id, uploaded_files = self.cleaned_data.get(field, (None, []))
|
||||
for uploaded_file in uploaded_files:
|
||||
uploaded_file.name = os.path.basename(uploaded_file.name)
|
||||
AttachedFile(
|
||||
document=instance,
|
||||
kind=attached_file_kind if attached_file_kind.id else None,
|
||||
name=truncate_filename(uploaded_file.name),
|
||||
content=uploaded_file,
|
||||
).save()
|
||||
|
||||
upload_id, uploaded_files = self.cleaned_data.get('content', (None, []))
|
||||
for uploaded_file in uploaded_files:
|
||||
uploaded_file.name = os.path.basename(uploaded_file.name)
|
||||
AttachedFile(document=instance,
|
||||
name=os.path.basename(uploaded_file.name),
|
||||
content=uploaded_file).save()
|
||||
|
||||
class ContactForm(Form):
|
||||
'''Form to contact administrators of the platform for logged in users.'''
|
||||
layout = Layout(
|
||||
'subject',
|
||||
'message',
|
||||
ButtonHolder(
|
||||
Submit('send', _('send your message'))),
|
||||
)
|
||||
helper = FormHelper()
|
||||
helper.form_method = 'POST'
|
||||
helper.add_layout(layout)
|
||||
helper.form_action = ''
|
||||
|
||||
subject = forms.CharField(max_length=100, label=_('Subject'), required=True)
|
||||
message = forms.CharField(
|
||||
widget=Textarea(attrs={'rows': 25, 'cols': 80}), label=_('Message'), required=True
|
||||
)
|
||||
|
||||
class Media:
|
||||
js = ('js/askdirtyform.js',)
|
||||
subject = forms.CharField(max_length=100, label=_('Subject'),
|
||||
required=True)
|
||||
message = forms.CharField(widget=Textarea(attrs={'rows': 25, 'cols': 80}),
|
||||
label=_('Message'), required=True)
|
||||
|
||||
|
||||
class AnonymousContactForm(ContactForm):
|
||||
'''Form to contact administrators of the platform for anonymous users.'''
|
||||
layout = Layout(
|
||||
'name',
|
||||
'email',
|
||||
'phone_number',
|
||||
'subject',
|
||||
'message',
|
||||
ButtonHolder(
|
||||
Submit('send', _('send your message'))),
|
||||
)
|
||||
helper = FormHelper()
|
||||
helper.form_method = 'POST'
|
||||
helper.add_layout(layout)
|
||||
helper.form_action = ''
|
||||
|
||||
name = forms.CharField(max_length=100, label=_('Name'), required=True)
|
||||
email = forms.EmailField(max_length=100, label=_('Email'), required=False)
|
||||
phone_number = forms.CharField(max_length=100, label=_('Phone number'), required=False)
|
||||
phone_number = forms.CharField(max_length=100, label=_('Phone number'),
|
||||
required=False)
|
||||
|
||||
|
||||
class MailingListForm(ModelForm):
|
||||
'''Admin form to edit MailingList objects'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
self.fields['members'].queryset = User.objects.all().order_by('username')
|
||||
self.fields['members'].label_from_instance = lambda y: username(y)
|
||||
|
||||
class Meta:
|
||||
model = MailingList
|
||||
fields = ('name', 'is_active', 'members', 'mailing_list_members')
|
||||
widgets = {
|
||||
'members': AdminFilteredSelectMultiple(_('Persons'), False),
|
||||
'members': AdminFilteredSelectMultiple(_('Persons'), False),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
'''Orders members by their username, use their username to display them.'''
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
self.fields['members'].queryset = non_guest_users().order_by('username')
|
||||
self.fields['members'].label_from_instance = lambda y: username(y)
|
||||
|
||||
|
||||
class UserChoiceField(ModelChoiceField):
|
||||
def label_from_instance(self, user):
|
||||
if user.first_name or user.last_name:
|
||||
return user.first_name + ' ' + user.last_name
|
||||
return user.username
|
||||
|
||||
|
||||
class DelegationForm(Form):
|
||||
'''Form to manager delegations of users'''
|
||||
|
||||
first_name = CharField(label=_('Firstname'), max_length=30, required=False)
|
||||
last_name = CharField(label=_('Lastname'), max_length=30, required=False)
|
||||
email = EmailField(label=_('Email'), required=False)
|
||||
existing_user = UserChoiceField(label=_('User'), required=False, queryset=User.objects.all())
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
self.delegatees = kwargs.pop('delegatees', [])
|
||||
self._request = kwargs.pop('request')
|
||||
super().__init__(*args, **kwargs)
|
||||
qs = non_guest_users()
|
||||
if self.user:
|
||||
qs = qs.exclude(id=self.user.id)
|
||||
if self.delegatees:
|
||||
qs = qs.exclude(id__in=[u.id for u in self.delegatees])
|
||||
self.fields['existing_user'].queryset = qs.order_by('first_name', 'last_name')
|
||||
if not app_settings.DELEGATE_TO_EXISTING_USER:
|
||||
del self.fields['existing_user']
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
ok1 = bool(cleaned_data.get('first_name'))
|
||||
ok2 = bool(cleaned_data.get('last_name'))
|
||||
ok3 = bool(cleaned_data.get('email'))
|
||||
new = ok1 and ok2 and ok3
|
||||
ok4 = bool(cleaned_data.get('existing_user'))
|
||||
if not ((ok1 or ok2 or ok3) ^ ok4):
|
||||
raise ValidationError(
|
||||
_(
|
||||
'You must choose between creating a new '
|
||||
'user or delegating to an existing one; the two are mutually '
|
||||
'exclusive.'
|
||||
)
|
||||
layout = Layout(
|
||||
'first_name', 'last_name', 'email',
|
||||
ButtonHolder(
|
||||
Submit('create', _('Create a new delegation'))),
|
||||
)
|
||||
if not new and (ok1 or ok2 or ok3):
|
||||
raise ValidationError(
|
||||
_('To create a new user you must give a first name, a last name and a valid email')
|
||||
)
|
||||
if new:
|
||||
email = cleaned_data.get('email')
|
||||
if email == self.user.email:
|
||||
raise ValidationError(_('Email is yours, you cannot delegate to yourself'))
|
||||
if any(delegate.email == email for delegate in self.delegatees):
|
||||
raise ValidationError(_('A delegation with the same email already exists'))
|
||||
qs = non_guest_users().filter(email=email)
|
||||
if qs.exists():
|
||||
list_of_names = ', '.join([user.get_full_name() for user in qs])
|
||||
self.data = {}
|
||||
self.is_bound = False
|
||||
raise ValidationError(
|
||||
_('This email belong to existing user(s) {0}, look in the list of existing users').format(
|
||||
list_of_names
|
||||
)
|
||||
)
|
||||
|
||||
if 'mellon' in app_settings.settings.INSTALLED_APPS:
|
||||
# Create user
|
||||
url = urllib.parse.urljoin(app_settings.settings.AUTHENTIC_URL, 'api/users/')
|
||||
json = {
|
||||
'first_name': cleaned_data['first_name'],
|
||||
'last_name': cleaned_data['last_name'],
|
||||
'email': email,
|
||||
'send_registration_email': True,
|
||||
'send_registration_email_next_url': self._request.build_absolute_uri('/'),
|
||||
}
|
||||
err, json_data, err_desc = a2_wscall(url, 'post', json)
|
||||
if err:
|
||||
raise ValidationError(err_desc)
|
||||
cleaned_data['name_id'] = json_data['uuid']
|
||||
|
||||
# Give created user a role
|
||||
role_uuid = getattr(app_settings.settings, 'AUTHENTIC_ROLE', None)
|
||||
if role_uuid:
|
||||
url = urllib.parse.urljoin(
|
||||
app_settings.settings.AUTHENTIC_URL,
|
||||
'api/roles/%s/members/%s/' % (role_uuid, json_data['uuid']),
|
||||
)
|
||||
err, json_data, err_desc = a2_wscall(url, 'post')
|
||||
if err:
|
||||
raise ValidationError(err_desc)
|
||||
|
||||
return cleaned_data
|
||||
helper = FormHelper()
|
||||
helper.form_method = 'POST'
|
||||
helper.add_layout(layout)
|
||||
helper.form_action = ''
|
||||
|
||||
first_name = CharField(label=_('Firstname'))
|
||||
last_name = CharField(label=_('Lastname'))
|
||||
email = EmailField(label=_('Email'))
|
||||
|
||||
class AutomaticForwardingForm(ModelForm):
|
||||
'''Admin form for editing AutomaticForwarding objects'''
|
||||
|
||||
class Meta:
|
||||
model = AutomaticForwarding
|
||||
fields = '__all__'
|
||||
|
||||
def clean(self):
|
||||
'''Validate that the forwarding rule contains at least one recipient.'''
|
||||
cleaned_data = super().clean()
|
||||
cleaned_data = super(AutomaticForwardingForm, self).clean()
|
||||
if not cleaned_data.get('forward_to_user') and not cleaned_data.get('forward_to_list'):
|
||||
raise ValidationError(_('A forwarding rule must have at least one recipient, person or list.'))
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class ProfileForm(ModelForm):
|
||||
"""User form for editing personal informations like email and mobile
|
||||
phone.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = DocbowProfile
|
||||
fields = ()
|
||||
if app_settings.MOBILE_PHONE:
|
||||
fields += ('accept_notifications', 'mobile_phone')
|
||||
if app_settings.PERSONAL_EMAIL:
|
||||
fields += ('personal_email',)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
"""Initialize the form object.
|
||||
Define a custom help text.
|
||||
"""
|
||||
self.request = request
|
||||
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
if app_settings.MOBILE_PHONE:
|
||||
self.fields['mobile_phone'].help_text = _(
|
||||
'Use international phone number '
|
||||
'format, i.e +[country code][number]. A challenge SMS will be sent to you to validate it.'
|
||||
)
|
||||
|
||||
def clean_mobile_phone(self):
|
||||
"""Validate the mobile phone number by sending a HMAC signature as a code by SMS
|
||||
to the phone number.
|
||||
|
||||
The HMAC code is valid for one day.
|
||||
"""
|
||||
if self.cleaned_data.get('mobile_phone'):
|
||||
mobile_phone = phone_normalize(self.cleaned_data['mobile_phone'])
|
||||
validate_fr_be_phone(mobile_phone)
|
||||
self.cleaned_data['mobile_phone'] = mobile_phone
|
||||
if not self.instance or self.instance.mobile_phone != mobile_phone:
|
||||
date = datetime.date.today()
|
||||
code = hmac.new(
|
||||
force_bytes(settings.SECRET_KEY), force_str(date) + mobile_phone, hashlib.sha1
|
||||
).hexdigest()
|
||||
code = '%06d' % (int(code, 16) % 1000000)
|
||||
key = '%s-code' % self.prefix if self.prefix else 'code'
|
||||
if self.data.get(key, '').strip() != code:
|
||||
|
||||
def send_sms(mobile_phone, code):
|
||||
try:
|
||||
sms_carrier = notification.SMSNotifier.get_carrier()
|
||||
sms_carrier.send_sms((mobile_phone,), 'code is ' + code, no_stop=False)
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception(
|
||||
'unable to send SMS verification code %r to %r', code, mobile_phone
|
||||
)
|
||||
self.request.record(
|
||||
'error',
|
||||
'unable to send SMS verification code {code} to {mobile_phone}',
|
||||
mobile_phone=mobile_phone,
|
||||
code=code,
|
||||
)
|
||||
return False
|
||||
|
||||
if not send_sms(mobile_phone, code):
|
||||
raise ValidationError(_('Unable to send you the SMS code, try again.'))
|
||||
self.fields['code'] = CharField(label='Code')
|
||||
raise ValidationError(_('Enter code received by SMS'))
|
||||
return self.cleaned_data.get('mobile_phone')
|
||||
|
||||
def save(self, commit=True):
|
||||
'''Attach current user to the newly created profile object.'''
|
||||
instance = ModelForm.save(self, commit=False)
|
||||
instance.user = self.request.user
|
||||
if commit:
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
def _unicode_ci_compare(s1, s2):
|
||||
"""
|
||||
Perform case-insensitive comparison of two identifiers, using the
|
||||
recommended algorithm from Unicode Technical Report 36, section
|
||||
2.11.2(B)(2).
|
||||
"""
|
||||
normalized1 = unicodedata.normalize('NFKC', s1)
|
||||
normalized2 = unicodedata.normalize('NFKC', s2)
|
||||
return normalized1.casefold() == normalized2.casefold()
|
||||
|
||||
|
||||
class PasswordResetFormWithLogging(PasswordResetForm):
|
||||
email = forms.EmailField(widget=forms.HiddenInput(), required=False)
|
||||
identifier = forms.CharField(label=_('E-mail or identifier'), max_length=75)
|
||||
|
||||
def clean_email(self):
|
||||
return None
|
||||
|
||||
def clean_identifier(self):
|
||||
"""
|
||||
Validates that an active user exists with the given email address.
|
||||
"""
|
||||
identifier = self.cleaned_data['identifier']
|
||||
self.users_cache = User.objects.filter(
|
||||
Q(email__iexact=identifier)
|
||||
| Q(username=identifier)
|
||||
| Q(docbowprofile__personal_email=identifier),
|
||||
is_active=True,
|
||||
).distinct()
|
||||
for user in self.users_cache:
|
||||
try:
|
||||
if not user.email or _unicode_ci_compare(user.docbowprofile.personal_email, identifier):
|
||||
user.email = user.docbowprofile.personal_email
|
||||
except DocbowProfile.DoesNotExist:
|
||||
pass
|
||||
self.users_cache = [user for user in self.users_cache if user.email]
|
||||
if not len(self.users_cache):
|
||||
raise forms.ValidationError(
|
||||
_(
|
||||
"That e-mail address or identifier doesn't have an associated user account. Are you sure you've registered?"
|
||||
)
|
||||
)
|
||||
return identifier
|
||||
|
||||
def get_users(self, *args):
|
||||
return self.users_cache
|
||||
|
||||
def save(
|
||||
self,
|
||||
domain_override=None,
|
||||
subject_template_name='registration/password_reset_subject.txt',
|
||||
email_template_name='registration/password_reset_email.html',
|
||||
use_https=False,
|
||||
token_generator=default_token_generator,
|
||||
from_email=None,
|
||||
request=None,
|
||||
html_email_template_name=None,
|
||||
extra_email_context=None,
|
||||
):
|
||||
"""
|
||||
Generates a one-use only link for resetting password and sends to the
|
||||
user.
|
||||
"""
|
||||
email = self.cleaned_data['email']
|
||||
for user in self.get_users(email):
|
||||
if not domain_override:
|
||||
current_site = get_current_site(request)
|
||||
site_name = current_site.name
|
||||
domain = current_site.domain
|
||||
else:
|
||||
site_name = domain = domain_override
|
||||
user_email = getattr(user, 'email')
|
||||
context = {
|
||||
'email': user_email,
|
||||
'domain': domain,
|
||||
'site_name': site_name,
|
||||
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
|
||||
'user': user,
|
||||
'token': token_generator.make_token(user),
|
||||
'protocol': 'https' if use_https else 'http',
|
||||
}
|
||||
if extra_email_context is not None:
|
||||
context.update(extra_email_context)
|
||||
self.send_mail(
|
||||
subject_template_name,
|
||||
email_template_name,
|
||||
context,
|
||||
from_email,
|
||||
user_email,
|
||||
html_email_template_name=html_email_template_name,
|
||||
)
|
||||
|
||||
for user in self.users_cache:
|
||||
django_journal.record(
|
||||
'password-reset',
|
||||
'password reset link sent to {email}',
|
||||
user=user,
|
||||
email=user.email,
|
||||
ip=get_extra()['ip'],
|
||||
)
|
||||
|
||||
|
||||
class PasswordChangeFormWithLogging(PasswordChangeForm):
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
django_journal.record('password-change', 'changed its email', user=self.user, ip=get_extra()['ip'])
|
||||
|
||||
|
||||
class FilterForm(forms.Form):
|
||||
not_before = forms.DateField(label=_('From'), required=False, localize=True)
|
||||
not_after = forms.DateField(label=_('To'), required=False, localize=True)
|
||||
search_terms = forms.CharField(label=_('Search terms'), required=False)
|
||||
|
||||
class Media:
|
||||
js = (
|
||||
'jquery-ui/js/jquery-ui-1.12.1-autocomplete-datepicker.min.js',
|
||||
'docbow/js/filter-form.js',
|
||||
)
|
||||
css = {
|
||||
'all': ('jquery-ui/css/jquery-ui-1.12.1.css',),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
request = kwargs.pop('request')
|
||||
outbox = kwargs.pop('outbox', False)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['search_terms'].widget.attrs['data-boxtype'] = 'outbox' if outbox else 'inbox'
|
||||
|
||||
for field in ('sort', 'page'):
|
||||
if field in request.GET:
|
||||
self.fields[field] = forms.CharField(initial=request.GET.get(field), widget=forms.HiddenInput)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
if (
|
||||
cleaned_data.get('not_before')
|
||||
and cleaned_data.get('not_after')
|
||||
and cleaned_data['not_before'] > cleaned_data['not_after']
|
||||
):
|
||||
raise ValidationError(_('From must be inferior or equal to To'))
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class EmailForm(ModelForm):
|
||||
old_email = forms.EmailField(
|
||||
label=_('Old email'), required=False, widget=forms.TextInput(attrs={'disabled': 'on'})
|
||||
)
|
||||
password = forms.CharField(label=_('Password'), widget=forms.PasswordInput())
|
||||
email = forms.EmailField(label=_('New email'), required=True, initial='')
|
||||
email2 = forms.EmailField(label=_('New email (repeated)'), required=True, widget=forms.TextInput())
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('email',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.initial['email'] = ''
|
||||
self.initial['old_email'] = self.instance.email
|
||||
|
||||
def clean_password(self):
|
||||
password = self.cleaned_data['password']
|
||||
if not self.instance.check_password(password):
|
||||
raise ValidationError(_('password incorrect'))
|
||||
return password
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
email = cleaned_data.get('email')
|
||||
email2 = cleaned_data.get('email2')
|
||||
if email and email2 and email != email2:
|
||||
self._errors['email'] = self.error_class([_('emails are not equal')])
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class NotificationPreferencesForm(Form):
|
||||
class Media:
|
||||
js = ('docbow/js/checkall.js',)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.user = request.user
|
||||
self.notifiers = notification.get_notifiers()
|
||||
self.filetypes = models.FileType.objects.all()
|
||||
self.choices = []
|
||||
self.initials = {}
|
||||
self.kinds = []
|
||||
for notifier in self.notifiers:
|
||||
self.choices.append((notifier.key, notifier.description))
|
||||
self.kinds.append(notifier.key)
|
||||
for filetype in self.filetypes:
|
||||
self.initials[filetype.id] = set(self.kinds)
|
||||
for np in models.NotificationPreference.objects.filter(user=self.user):
|
||||
if not np.value:
|
||||
self.initials[np.filetype_id].remove(np.kind)
|
||||
super().__init__(*args, **kwargs)
|
||||
for filetype in self.filetypes:
|
||||
key = 'filetype-%s' % filetype.id
|
||||
self.fields[key] = forms.MultipleChoiceField(
|
||||
label=force_str(filetype),
|
||||
choices=self.choices,
|
||||
initial=self.initials[filetype.id],
|
||||
widget=widgets.CheckboxMultipleSelect,
|
||||
required=False,
|
||||
)
|
||||
|
||||
def save(self):
|
||||
cleaned_data = self.cleaned_data
|
||||
adds = collections.defaultdict(lambda: [])
|
||||
removes = collections.defaultdict(lambda: [])
|
||||
for key in cleaned_data:
|
||||
filetype_id = int(key.split('-')[1])
|
||||
new = set(cleaned_data[key])
|
||||
old = self.initials[filetype_id]
|
||||
remove = old - new
|
||||
for kind in remove:
|
||||
removes[kind].append(filetype_id)
|
||||
for kind in new - old:
|
||||
adds[kind].append(filetype_id)
|
||||
for kind in adds:
|
||||
models.NotificationPreference.objects.filter(
|
||||
user=self.user, kind=kind, filetype__in=adds[kind]
|
||||
).delete()
|
||||
for kind in removes:
|
||||
for filetype_id in removes[kind]:
|
||||
filetype = models.FileType.objects.get(id=filetype_id)
|
||||
np, created = models.NotificationPreference.objects.get_or_create(
|
||||
user=self.user, kind=kind, filetype=filetype, value=False
|
||||
)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import logging
|
||||
|
||||
|
||||
class ForceDebugFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
record.levelno = logging.DEBUG
|
||||
record.levelname = 'DEBUG'
|
||||
return super().filter(record)
|
|
@ -0,0 +1,12 @@
|
|||
import logging
|
||||
|
||||
class DjangoLoggerAdapter(logging.LoggerAdapter):
|
||||
def __init__(self, logger, request):
|
||||
user = request.user
|
||||
if hasattr(user, 'delegate'):
|
||||
user = '%s/%s' % (user.delegate, user)
|
||||
extra = { 'ip': request.META.get('REMOTE_ADDR', '-'), 'user': user }
|
||||
logging.LoggerAdapter.__init__(self, logger, extra)
|
||||
|
||||
def get_logger(request, domain='docbow.views'):
|
||||
return DjangoLoggerAdapter(logging.getLogger(domain), request)
|
|
@ -1,57 +0,0 @@
|
|||
import locale
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from docbow_project.docbow.models import MailingList
|
||||
|
||||
|
||||
def get_object(model, ref):
|
||||
'''Try to get a model by id or by name'''
|
||||
if ref.isdigit():
|
||||
return model.objects.get(id=int(ref))
|
||||
else:
|
||||
return model.objects.get(name=ref)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Create or update a list'''
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('ml_name', type=str, help='Name of the mailing list')
|
||||
parser.add_argument(
|
||||
'--add-list',
|
||||
action='append',
|
||||
help='add a list as a sublist',
|
||||
default=[],
|
||||
)
|
||||
parser.add_argument(
|
||||
'--remove-list',
|
||||
action='append',
|
||||
help='remove list as a sublist',
|
||||
default=[],
|
||||
)
|
||||
parser.add_argument('--add-user', action='append', help='add a user member', default=[])
|
||||
parser.add_argument('--remove-user', action='append', help='remove a user member', default=[])
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, **options):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
locale_encoding = locale.nl_langinfo(locale.CODESET)
|
||||
mailing_list, created = MailingList.objects.get_or_create(
|
||||
name=force_str(options['ml_name'], locale_encoding)
|
||||
)
|
||||
for name in options['add_list']:
|
||||
ml = get_object(MailingList, name)
|
||||
mailing_list.mailing_list_members.add(ml)
|
||||
for name in options['remove_list']:
|
||||
ml = get_object(MailingList, name)
|
||||
mailing_list.mailing_list_members.remove(ml)
|
||||
for g in options['add_user']:
|
||||
g = get_object(User, g)
|
||||
mailing_list.members.add(g)
|
||||
for g in options['remove_user']:
|
||||
g = get_object(User, g)
|
||||
mailing_list.members.remove(g)
|
|
@ -1,79 +0,0 @@
|
|||
from optparse import make_option
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from docbow_project.docbow.models import DocbowProfile, MailingList
|
||||
|
||||
|
||||
def get_object(model, ref):
|
||||
'''Try to get a model by id or by name'''
|
||||
if ref.isdigit():
|
||||
return model.objects.get(id=int(ref))
|
||||
else:
|
||||
return model.objects.get(name=ref)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = '<username>'
|
||||
help = '''Create a new user or update an existing user
|
||||
|
||||
List and groups can be referred by name or by id.
|
||||
'''
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--first-name', help='set first name'),
|
||||
make_option('--last-name', help='set last name'),
|
||||
make_option('--email', help='set email'),
|
||||
make_option('--mobile-phone', help='set mobile phone used for SMS notifications'),
|
||||
make_option('--personal-email', help='set personal email'),
|
||||
make_option('--add-list', help='add user to list', action='append', default=[]),
|
||||
make_option('--add-group', help='add user to group', action='append', default=[]),
|
||||
make_option('--remove-list', help='remove user from list', action='append', default=[]),
|
||||
make_option('--remove-group', help='remove user from group', action='append', default=[]),
|
||||
make_option(
|
||||
'--activate', action='store_true', help='activate the user (default at creation)', default=None
|
||||
),
|
||||
make_option('--deactivate', dest='activate', action='store_false', help='deactivate the user'),
|
||||
make_option('--superuser', action='store_true', help='set the superuser flag', default=None),
|
||||
make_option(
|
||||
'--no-superuser', dest='superuser', action='store_false', help='unset the superuser flag'
|
||||
),
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
if len(args) != 1:
|
||||
raise CommandError('username argument is mandatory')
|
||||
user, created = User.objects.get_or_create(username=args[0])
|
||||
if options['first_name']:
|
||||
user.first_name = force_str(options['first_name'], 'utf-8')
|
||||
if options['last_name']:
|
||||
user.last_name = force_str(options['last_name'], 'utf-8')
|
||||
if options['email']:
|
||||
user.email = force_str(options['email'], 'utf-8')
|
||||
if options['activate'] is not None:
|
||||
user.is_active = options['activate']
|
||||
if options['superuser'] is not None:
|
||||
user.is_superuser = options['superuser']
|
||||
for name in options['add_list']:
|
||||
ml = get_object(MailingList, name)
|
||||
ml.members.add(user)
|
||||
for name in options['remove_list']:
|
||||
ml = get_object(MailingList, name)
|
||||
ml.members.remove(user)
|
||||
for g in options['add_group']:
|
||||
g = get_object(Group, g)
|
||||
user.groups.add(g)
|
||||
for g in options['remove_group']:
|
||||
g = get_object(Group, g)
|
||||
user.groups.remove(g)
|
||||
profile, created = DocbowProfile.objects.get_or_create(user=user)
|
||||
if options['mobile_phone']:
|
||||
profile.mobile_phone = force_str(options['mobile_phone'], 'utf-8')
|
||||
if options['personal_email']:
|
||||
profile.personal_email = force_str(options['personal_email'], 'utf-8')
|
||||
user.save()
|
||||
profile.save()
|
|
@ -1,18 +1,17 @@
|
|||
import shutil
|
||||
import csv
|
||||
import datetime as dt
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
import time
|
||||
import datetime as dt
|
||||
|
||||
import django.contrib.auth.models as auth_models
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
import django.contrib.auth.models as auth_models
|
||||
from django.db import transaction
|
||||
from django.conf import settings
|
||||
|
||||
from ....log import models as log_models
|
||||
from ... import models
|
||||
|
||||
from ... import timestamp
|
||||
from ....log import models as log_models
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = '<directory>'
|
||||
|
@ -24,7 +23,12 @@ class Command(BaseCommand):
|
|||
|
||||
def save_users(self, path):
|
||||
with open(os.path.join(path, 'users.csv'), 'w') as f:
|
||||
headers = ['username', 'prenom', 'nom', 'email', 'profil', 'groupe']
|
||||
headers = ['username',
|
||||
'prenom',
|
||||
'nom',
|
||||
'email',
|
||||
'profil',
|
||||
'groupe']
|
||||
csv_handle = csv.DictWriter(f, headers)
|
||||
users = auth_models.User.objects.filter(is_superuser=False)
|
||||
csv_handle.writerow(dict(zip(csv_handle.fieldnames, csv_handle.fieldnames)))
|
||||
|
@ -37,20 +41,24 @@ class Command(BaseCommand):
|
|||
'nom': user.last_name,
|
||||
'email': user.email,
|
||||
'profil': ','.join([ml.name for ml in user.mailing_lists.all()]),
|
||||
'groupe': ','.join([group.name for group in user.groups.all()]),
|
||||
}
|
||||
'groupe': ','.join([group.name for group in user.groups.all()])}
|
||||
self.dict_to_utf8(d)
|
||||
csv_handle.writerow(d)
|
||||
users.delete()
|
||||
|
||||
def save_logs(self, path):
|
||||
with open(os.path.join(path, 'log.csv'), 'w') as f:
|
||||
headers = ['timestamp', 'name', 'levelname', 'ip', 'user', 'message']
|
||||
headers = ['timestamp',
|
||||
'name',
|
||||
'levelname',
|
||||
'ip',
|
||||
'user',
|
||||
'message']
|
||||
csv_handle = csv.DictWriter(f, headers)
|
||||
csv_handle.writerow(dict(zip(csv_handle.fieldnames, csv_handle.fieldnames)))
|
||||
logs = log_models.LogLine.objects.all()
|
||||
for log in logs:
|
||||
d = {header: getattr(log, header) for header in headers}
|
||||
d = dict([(header, getattr(log, header)) for header in headers])
|
||||
d['timestamp'] = d['timestamp'].isoformat()
|
||||
self.dict_to_utf8(d)
|
||||
csv_handle.writerow(d)
|
||||
|
@ -58,17 +66,19 @@ class Command(BaseCommand):
|
|||
|
||||
def timestamp_logs(self, path):
|
||||
with open(os.path.join(path, 'log-timestamp.der'), 'w') as f:
|
||||
with open(os.path.join(path, 'log.csv')):
|
||||
tst = str(time.time())
|
||||
with open(os.path.join(path, 'log.csv')) as log_handle:
|
||||
tst, message = timestamp.timestamp(log_handle.read())
|
||||
if not tst:
|
||||
raise CommandError('Failure to compute the timestamp: %s' % message)
|
||||
f.write(tst)
|
||||
|
||||
def remove_profiles(self):
|
||||
models.MailingList.objects.all().delete()
|
||||
|
||||
def removal_error(self, function, path, excinfo):
|
||||
print('unable to delete %s: %s' % (path, excinfo[1]))
|
||||
print 'unable to delete %s: %s' % (path, excinfo[1])
|
||||
|
||||
@transaction.atomic
|
||||
@transaction.commit_on_success
|
||||
def handle(self, *args, **options):
|
||||
directory = args[0]
|
||||
path = os.path.join(directory, dt.datetime.now().isoformat())
|
||||
|
@ -79,14 +89,14 @@ class Command(BaseCommand):
|
|||
self.save_logs(path)
|
||||
self.timestamp_logs(path)
|
||||
file_dir = os.path.join(settings.MEDIA_ROOT, 'files/')
|
||||
print('Removing %s...' % file_dir)
|
||||
print 'Removing %s...' % file_dir
|
||||
shutil.rmtree(file_dir, onerror=self.removal_error)
|
||||
upload_dir = os.path.join(settings.MEDIA_ROOT, 'upload/')
|
||||
print('Removing %s...' % upload_dir)
|
||||
print 'Removing %s...' % upload_dir
|
||||
shutil.rmtree(upload_dir, onerror=self.removal_error)
|
||||
print('Creating %s...' % file_dir)
|
||||
print 'Creating %s...' % file_dir
|
||||
os.mkdir(file_dir)
|
||||
print('Creating %s...' % upload_dir)
|
||||
print 'Creating %s...' % upload_dir
|
||||
os.mkdir(upload_dir)
|
||||
except:
|
||||
shutil.rmtree(path)
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from ... import models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
'''Undelete all documents'''
|
||||
|
||||
help = 'Undelete all documents'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
count = models.Document.objects.count()
|
||||
models.Document.objects.all().delete()
|
||||
print('Definitively deleted %d documents.' % count)
|
||||
print "Definitively deleted %d documents." % count
|
||||
|
|
|
@ -1,240 +0,0 @@
|
|||
from optparse import make_option
|
||||
|
||||
from django.core import serializers
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import DEFAULT_DB_ALIAS, router
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option(
|
||||
'--format',
|
||||
default='json',
|
||||
dest='format',
|
||||
help='Specifies the output serialization format for fixtures.',
|
||||
),
|
||||
make_option(
|
||||
'--indent',
|
||||
default=None,
|
||||
dest='indent',
|
||||
type='int',
|
||||
help='Specifies the indent level to use when pretty-printing output',
|
||||
),
|
||||
make_option(
|
||||
'--database',
|
||||
action='store',
|
||||
dest='database',
|
||||
default=DEFAULT_DB_ALIAS,
|
||||
help='Nominates a specific database to dump '
|
||||
'fixtures from. Defaults to the "default" database.',
|
||||
),
|
||||
make_option(
|
||||
'-e',
|
||||
'--exclude',
|
||||
dest='exclude',
|
||||
action='append',
|
||||
default=[],
|
||||
help='An appname or appname.ModelName to exclude (use multiple --exclude to exclude multiple apps/models).',
|
||||
),
|
||||
make_option(
|
||||
'-n',
|
||||
'--natural',
|
||||
action='store_true',
|
||||
dest='use_natural_keys',
|
||||
default=False,
|
||||
help='Use natural keys if they are available.',
|
||||
),
|
||||
make_option(
|
||||
'-a',
|
||||
'--all',
|
||||
action='store_true',
|
||||
dest='use_base_manager',
|
||||
default=False,
|
||||
help="Use Django's base manager to dump all models stored in the database, including those that would otherwise be filtered or modified by a custom manager.",
|
||||
),
|
||||
)
|
||||
help = (
|
||||
'Output the contents of the database as a fixture of the given '
|
||||
"format (using each model's default manager unless --all is "
|
||||
'specified).'
|
||||
)
|
||||
args = '[appname appname.ModelName ...]'
|
||||
|
||||
def handle(self, *app_labels, **options):
|
||||
from django.db.models import get_app, get_apps, get_model, get_models
|
||||
|
||||
format = options.get('format')
|
||||
indent = options.get('indent')
|
||||
using = options.get('database')
|
||||
excludes = options.get('exclude')
|
||||
show_traceback = options.get('traceback')
|
||||
use_natural_keys = options.get('use_natural_keys')
|
||||
use_base_manager = options.get('use_base_manager')
|
||||
|
||||
excluded_apps = set()
|
||||
excluded_models = set()
|
||||
for exclude in excludes:
|
||||
if '.' in exclude:
|
||||
app_label, model_name = exclude.split('.', 1)
|
||||
model_obj = get_model(app_label, model_name)
|
||||
if not model_obj:
|
||||
raise CommandError('Unknown model in excludes: %s' % exclude)
|
||||
excluded_models.add(model_obj)
|
||||
else:
|
||||
try:
|
||||
app_obj = get_app(exclude)
|
||||
excluded_apps.add(app_obj)
|
||||
except ImproperlyConfigured:
|
||||
raise CommandError('Unknown app in excludes: %s' % exclude)
|
||||
|
||||
if len(app_labels) == 0:
|
||||
app_list = SortedDict(
|
||||
(app, [model for model in get_models(app) if model not in excluded_models])
|
||||
for app in get_apps()
|
||||
if app not in excluded_apps
|
||||
)
|
||||
else:
|
||||
app_list = SortedDict()
|
||||
for label in app_labels:
|
||||
try:
|
||||
app_label, model_label = label.split('.')
|
||||
try:
|
||||
app = get_app(app_label)
|
||||
except ImproperlyConfigured:
|
||||
raise CommandError('Unknown application: %s' % app_label)
|
||||
if app in excluded_apps:
|
||||
continue
|
||||
model = get_model(app_label, model_label)
|
||||
if model is None:
|
||||
raise CommandError('Unknown model: %s.%s' % (app_label, model_label))
|
||||
if model in excluded_models:
|
||||
continue
|
||||
if app in app_list.keys():
|
||||
if app_list[app] and model not in app_list[app]:
|
||||
app_list[app].append(model)
|
||||
else:
|
||||
app_list[app] = [model]
|
||||
except ValueError:
|
||||
# This is just an app - no model qualifier
|
||||
app_label = label
|
||||
try:
|
||||
app = get_app(app_label)
|
||||
except ImproperlyConfigured:
|
||||
raise CommandError('Unknown application: %s' % app_label)
|
||||
if app in excluded_apps:
|
||||
continue
|
||||
app_list[app] = None
|
||||
|
||||
# Check that the serialization format exists; this is a shortcut to
|
||||
# avoid collating all the objects and _then_ failing.
|
||||
if format not in serializers.get_public_serializer_formats():
|
||||
raise CommandError('Unknown serialization format: %s' % format)
|
||||
|
||||
try:
|
||||
serializers.get_serializer(format)
|
||||
except KeyError:
|
||||
raise CommandError('Unknown serialization format: %s' % format)
|
||||
|
||||
# Now collate the objects to be serialized.
|
||||
objects = []
|
||||
for model in sort_dependencies(app_list.items()):
|
||||
if model in excluded_models:
|
||||
continue
|
||||
if not model._meta.proxy and router.allow_syncdb(using, model):
|
||||
if use_base_manager:
|
||||
objects.extend(model._base_manager.using(using).all())
|
||||
else:
|
||||
objects.extend(model._default_manager.using(using).all())
|
||||
|
||||
try:
|
||||
return serializers.serialize(
|
||||
format, objects, indent=indent, use_natural_foreign_keys=use_natural_keys
|
||||
)
|
||||
except Exception as e:
|
||||
if show_traceback:
|
||||
raise
|
||||
raise CommandError('Unable to serialize database: %s' % e)
|
||||
|
||||
|
||||
def sort_dependencies(app_list):
|
||||
"""Sort a list of app,modellist pairs into a single list of models.
|
||||
|
||||
The single list of models is sorted so that any model with a natural key
|
||||
is serialized before a normal model, and any model with a natural key
|
||||
dependency has it's dependencies serialized first.
|
||||
"""
|
||||
from django.db.models import get_model, get_models
|
||||
|
||||
# Process the list of models, and get the list of dependencies
|
||||
model_dependencies = []
|
||||
models = set()
|
||||
for app, model_list in app_list:
|
||||
if model_list is None:
|
||||
model_list = get_models(app)
|
||||
|
||||
for model in model_list:
|
||||
models.add(model)
|
||||
# Add any explicitly defined dependencies
|
||||
if hasattr(model, 'natural_key'):
|
||||
deps = getattr(model.natural_key, 'dependencies', [])
|
||||
if deps:
|
||||
deps = [get_model(*d.split('.')) for d in deps]
|
||||
else:
|
||||
deps = []
|
||||
|
||||
# Now add a dependency for any FK or M2M relation with
|
||||
# a model that defines a natural key
|
||||
for field in model._meta.fields:
|
||||
if hasattr(field.rel, 'to'):
|
||||
rel_model = field.rel.to
|
||||
if hasattr(rel_model, 'natural_key'):
|
||||
deps.append(rel_model)
|
||||
for field in model._meta.many_to_many:
|
||||
rel_model = field.rel.to
|
||||
if hasattr(rel_model, 'natural_key'):
|
||||
deps.append(rel_model)
|
||||
# Remove circular dependencies
|
||||
deps = filter(lambda x: x != model, deps)
|
||||
model_dependencies.append((model, deps))
|
||||
|
||||
model_dependencies.reverse()
|
||||
# Now sort the models to ensure that dependencies are met. This
|
||||
# is done by repeatedly iterating over the input list of models.
|
||||
# If all the dependencies of a given model are in the final list,
|
||||
# that model is promoted to the end of the final list. This process
|
||||
# continues until the input list is empty, or we do a full iteration
|
||||
# over the input models without promoting a model to the final list.
|
||||
# If we do a full iteration without a promotion, that means there are
|
||||
# circular dependencies in the list.
|
||||
model_list = []
|
||||
while model_dependencies:
|
||||
skipped = []
|
||||
changed = False
|
||||
while model_dependencies:
|
||||
model, deps = model_dependencies.pop()
|
||||
|
||||
# If all of the models in the dependency list are either already
|
||||
# on the final model list, or not on the original serialization list,
|
||||
# then we've found another model with all it's dependencies satisfied.
|
||||
found = True
|
||||
for candidate in ((d not in models or d in model_list) for d in deps):
|
||||
if not candidate:
|
||||
found = False
|
||||
if found:
|
||||
model_list.append(model)
|
||||
changed = True
|
||||
else:
|
||||
skipped.append((model, deps))
|
||||
if not changed:
|
||||
raise CommandError(
|
||||
"Can't resolve dependencies for %s in serialized app list."
|
||||
% ', '.join(
|
||||
'%s.%s' % (model._meta.app_label, model._meta.object_name)
|
||||
for model, deps in sorted(skipped, key=lambda obj: obj[0].__name__)
|
||||
)
|
||||
)
|
||||
model_dependencies = skipped
|
||||
|
||||
return model_list
|
|
@ -1,9 +1,10 @@
|
|||
import csv
|
||||
|
||||
import django.contrib.auth.models as auth_models
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
import django.contrib.auth.models as auth_models
|
||||
from django.db import transaction
|
||||
|
||||
from ... import models
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = '<directory>'
|
||||
|
@ -15,7 +16,13 @@ class Command(BaseCommand):
|
|||
|
||||
def save_users(self, path):
|
||||
with open(path, 'w') as f:
|
||||
headers = ['username', 'password', 'prenom', 'nom', 'email', 'profil', 'groupe']
|
||||
headers = ['username',
|
||||
'password',
|
||||
'prenom',
|
||||
'nom',
|
||||
'email',
|
||||
'profil',
|
||||
'groupe']
|
||||
csv_handle = csv.DictWriter(f, headers)
|
||||
users = auth_models.User.objects.filter(is_superuser=False, delegations_by__isnull=True)
|
||||
csv_handle.writerow(dict(zip(csv_handle.fieldnames, csv_handle.fieldnames)))
|
||||
|
@ -29,12 +36,11 @@ class Command(BaseCommand):
|
|||
'email': user.email,
|
||||
'password': user.password,
|
||||
'profil': ','.join([ml.name for ml in user.mailing_lists.all()]),
|
||||
'groupe': ','.join([group.name for group in user.groups.all()]),
|
||||
}
|
||||
'groupe': ','.join([group.name for group in user.groups.all()])}
|
||||
self.dict_to_utf8(d)
|
||||
csv_handle.writerow(d)
|
||||
|
||||
@transaction.atomic
|
||||
@transaction.commit_on_success
|
||||
def handle(self, *args, **options):
|
||||
path = args[0]
|
||||
self.save_users(path)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.utils.timezone import now
|
||||
|
||||
from docbow_project.docbow.models import DeletedDocument
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Empty trash'
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **kwargs):
|
||||
target_date = now() - timedelta(days=settings.TRASH_DURATION)
|
||||
for deleted_doc in DeletedDocument.objects.filter(soft_delete=True).filter(
|
||||
soft_delete_date__lte=target_date
|
||||
):
|
||||
deleted_doc.soft_delete = False
|
||||
deleted_doc.save()
|
|
@ -1,59 +0,0 @@
|
|||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from docbow_project.docbow.models import Document, Inbox
|
||||
from docbow_project.docbow.utils import date_to_aware_datetime
|
||||
|
||||
|
||||
def valid_date(s):
|
||||
try:
|
||||
return date_to_aware_datetime(datetime.strptime(s, '%Y-%m-%d'))
|
||||
except ValueError:
|
||||
msg = f"Not a valid date: '{s}'."
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Forward documents received by a user to another'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('from_user', type=int)
|
||||
parser.add_argument('to_user', type=int)
|
||||
parser.add_argument(
|
||||
'-s', '--startdate', required=False, type=valid_date, help='The start date - format YYYY-MM-DD'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-e', '--enddate', required=False, type=valid_date, help='The end date - format YYYY-MM-DD'
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, from_user, to_user, *args, **kwargs):
|
||||
verbose = kwargs.get('verbosity') > 1
|
||||
|
||||
from_user = User.objects.get(pk=from_user)
|
||||
to_user = User.objects.get(pk=to_user)
|
||||
from_user_inboxes = Inbox.objects.filter(owner=from_user, outbox=False)
|
||||
to_user_inboxes = Inbox.objects.filter(owner=to_user, outbox=False)
|
||||
docs = (
|
||||
Document.objects.exclude(deleteddocument__user=from_user)
|
||||
.filter(mailboxes__in=from_user_inboxes)
|
||||
.exclude(mailboxes__in=to_user_inboxes)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
startdate, enddate = kwargs.get('startdate'), kwargs.get('enddate')
|
||||
if startdate:
|
||||
docs = docs.filter(date__gte=startdate)
|
||||
if enddate:
|
||||
docs = docs.filter(date__lt=enddate)
|
||||
|
||||
if verbose:
|
||||
print('Prepare forwarding', docs.count(), 'from', from_user, 'to', to_user)
|
||||
for doc in docs:
|
||||
doc.forward(doc.sender, [], [to_user])
|
||||
if verbose:
|
||||
print('Forward ', doc)
|
|
@ -1,52 +0,0 @@
|
|||
import locale
|
||||
import sys
|
||||
from optparse import make_option
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from ...models import MailingList
|
||||
from ...unicodecsv import UnicodeWriter
|
||||
|
||||
|
||||
def print_table(table):
|
||||
col_width = [max(len(x) for x in col) for col in zip(*table)]
|
||||
for line in table:
|
||||
line = '| ' + ' | '.join('{0:>{1}}'.format(x, col_width[i]) for i, x in enumerate(line)) + ' |'
|
||||
print(line)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = ''
|
||||
help = '''List mailing lists'''
|
||||
|
||||
option_list = BaseCommand.option_list + (make_option('--csv', action='store_true'),)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
locale_encoding = locale.nl_langinfo(locale.CODESET)
|
||||
mailing_lists = MailingList.objects.prefetch_related('members', 'mailing_list_members')
|
||||
for arg in args:
|
||||
key, value = arg.split('=')
|
||||
mailing_lists = mailing_lists.filter(**{key: value})
|
||||
tables = [('Id', 'Name', 'Members', 'List Members')]
|
||||
for mailing_list in mailing_lists:
|
||||
tables.append(
|
||||
map(
|
||||
force_str,
|
||||
(
|
||||
mailing_list.id,
|
||||
mailing_list.name,
|
||||
','.join(m.name for m in mailing_list.mailing_list_members.all()),
|
||||
','.join(g.username for g in mailing_list.members.all()),
|
||||
),
|
||||
)
|
||||
)
|
||||
if options['csv']:
|
||||
writer = UnicodeWriter(sys.stdout, encoding=locale_encoding)
|
||||
for row in tables:
|
||||
writer.writerow(row)
|
||||
else:
|
||||
print_table(tables)
|
|
@ -1,79 +0,0 @@
|
|||
import locale
|
||||
import sys
|
||||
from optparse import make_option
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from ...models import DocbowProfile
|
||||
from ...unicodecsv import UnicodeWriter
|
||||
|
||||
|
||||
def print_table(table):
|
||||
col_width = [max(len(x) for x in col) for col in zip(*table)]
|
||||
for line in table:
|
||||
line = '| ' + ' | '.join('{0:>{1}}'.format(x, col_width[i]) for i, x in enumerate(line)) + ' |'
|
||||
print(line)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = ''
|
||||
help = '''List users'''
|
||||
|
||||
option_list = BaseCommand.option_list + (make_option('--csv', action='store_true'),)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
users = User.objects.prefetch_related('docbowprofile', 'groups', 'mailing_lists')
|
||||
for arg in args:
|
||||
key, value = arg.split('=')
|
||||
users = users.filter(**{key: value})
|
||||
tables = [
|
||||
(
|
||||
'Id',
|
||||
'Username',
|
||||
'First name',
|
||||
'Last name',
|
||||
'Email',
|
||||
'Mobile phone',
|
||||
'Personal mail',
|
||||
'Lists',
|
||||
'Groups',
|
||||
'Active',
|
||||
'Superuser',
|
||||
)
|
||||
]
|
||||
for user in users:
|
||||
try:
|
||||
mobile_phone = user.docbowprofile.mobile_phone
|
||||
personal_email = user.docbowprofile.personal_email
|
||||
except DocbowProfile.DoesNotExist:
|
||||
mobile_phone = ''
|
||||
personal_email = ''
|
||||
tables.append(
|
||||
map(
|
||||
force_str,
|
||||
(
|
||||
user.id,
|
||||
user.username,
|
||||
user.first_name,
|
||||
user.last_name,
|
||||
user.email,
|
||||
mobile_phone,
|
||||
personal_email,
|
||||
','.join(m.name for m in user.mailing_lists.all()),
|
||||
','.join(g.name for g in user.groups.all()),
|
||||
user.is_active,
|
||||
user.is_superuser,
|
||||
),
|
||||
)
|
||||
)
|
||||
if options['csv']:
|
||||
writer = UnicodeWriter(sys.stdout, encoding=locale.nl_langinfo(locale.CODESET))
|
||||
for row in tables:
|
||||
writer.writerow(row)
|
||||
else:
|
||||
print_table(tables)
|
|
@ -1,68 +1,59 @@
|
|||
import csv
|
||||
import functools
|
||||
import os.path
|
||||
from optparse import make_option
|
||||
import unicodedata
|
||||
import random
|
||||
import sys
|
||||
import unicodedata
|
||||
from optparse import make_option
|
||||
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils.encoding import force_str
|
||||
from django.contrib.auth import models as auth_models
|
||||
|
||||
from ... import models
|
||||
|
||||
|
||||
def strip_accents(s):
|
||||
return ''.join(c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn')
|
||||
|
||||
return ''.join((c for c in unicodedata.normalize('NFD', s)
|
||||
if unicodedata.category(c) != 'Mn'))
|
||||
|
||||
def keep_letters(s):
|
||||
return ''.join([c for c in s if c.isalpha()])
|
||||
|
||||
|
||||
def unicode_csv_reader(utf8_csv_data, dialect=csv.excel, **kwargs):
|
||||
# csv.py doesn't do Unicode; encode temporarily as UTF-8:
|
||||
csv_reader = csv.reader(utf8_csv_data, dialect=dialect, **kwargs)
|
||||
csv_reader = csv.reader(utf8_csv_data,
|
||||
dialect=dialect, **kwargs)
|
||||
for row in csv_reader:
|
||||
# decode UTF-8 back to Unicode, cell by cell:
|
||||
yield [force_str(cell, 'utf-8') for cell in row]
|
||||
|
||||
yield [unicode(cell, 'utf-8') for cell in row]
|
||||
|
||||
def csv_to_list(s):
|
||||
return filter(None, map(str.strip, s.split(',')))
|
||||
|
||||
return filter(None, map(unicode.strip, s.split(u',')))
|
||||
|
||||
# Utilise seulement des majuscules et des chiffres, sauf i,l et 1, O et 0
|
||||
__pwd_alphabet = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'
|
||||
|
||||
|
||||
def create_password(pwd_length=8):
|
||||
password = ''.join([random.choice(__pwd_alphabet) for x in range(pwd_length)])
|
||||
password = ''.join([random.choice(__pwd_alphabet)
|
||||
for x in range(pwd_length)])
|
||||
return password
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = '[--profile default_profile1,default_profile2] [--group defaut_group1,default_group2] [--password default_password] [--generate-password] file.csv'
|
||||
help = 'Load a CSV file containg user definitions'
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--profile', action='append', default=[]),
|
||||
make_option('--group', action='append', default=[]),
|
||||
make_option('--password', action='store'),
|
||||
make_option('--activate', action='store_true'),
|
||||
make_option('--generate-password', action='store_true', dest='generate_password'),
|
||||
)
|
||||
make_option("--profile", action='append', default=[]),
|
||||
make_option("--group", action='append', default=[]),
|
||||
make_option("--password", action='store'),
|
||||
make_option("--generate-password", action='store_true', dest='generate_password'),
|
||||
)
|
||||
|
||||
headers = {
|
||||
'nom': 'last_name',
|
||||
'prenom': 'first_name',
|
||||
'email': 'email',
|
||||
'profil': None,
|
||||
'username': None,
|
||||
'groupe': None,
|
||||
'password': None,
|
||||
}
|
||||
headers = { 'nom': 'last_name',
|
||||
'prenom': 'first_name',
|
||||
'email': 'email',
|
||||
'profil': None,
|
||||
'username': None,
|
||||
'groupe': None,
|
||||
'password': None }
|
||||
|
||||
def synthesis(self, data, **options):
|
||||
if not data.get('username'):
|
||||
|
@ -70,38 +61,38 @@ class Command(BaseCommand):
|
|||
raise CommandError('Username or nom/prenom must be given')
|
||||
prenom = keep_letters(strip_accents(data['prenom'])).lower()
|
||||
nom = keep_letters(strip_accents(data['nom'])).lower()
|
||||
username = '%s.%s' % (prenom, nom)
|
||||
username = "%s.%s" % (prenom, nom)
|
||||
data['username'] = username
|
||||
|
||||
if 'profil' not in data:
|
||||
default_profiles = csv_to_list(','.join(map(force_str, options.get('profile', []))))
|
||||
default_profiles = csv_to_list(','.join(map(unicode, options.get('profile',[]))))
|
||||
data['profil'] = default_profiles
|
||||
else:
|
||||
data['profil'] = csv_to_list(data['profil'])
|
||||
|
||||
if 'groupe' not in data:
|
||||
default_groups = csv_to_list(','.join(map(force_str, options.get('group', []))))
|
||||
default_groups = csv_to_list(','.join(map(unicode, options.get('group',[]))))
|
||||
data['groupe'] = default_groups
|
||||
else:
|
||||
data['groupe'] = csv_to_list(data['groupe'])
|
||||
|
||||
if not data.get('password'):
|
||||
if options.get('password'):
|
||||
data['password'] = force_str(options['password'], 'utf8')
|
||||
data['password'] = unicode(options['password'], 'utf8')
|
||||
elif options.get('generate_password', False):
|
||||
data['password'] = force_str(create_password())
|
||||
data['password'] = unicode(create_password())
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args) == 0:
|
||||
raise CommandError('missing filename')
|
||||
raise CommandError("missing filename")
|
||||
if not os.path.exists(args[0]):
|
||||
raise CommandError('%s not found' % args[0])
|
||||
tuples = unicode_csv_reader(open(args[0]), dialect='excel')
|
||||
raise CommandError("%s not found" % args[0])
|
||||
tuples = unicode_csv_reader(file(args[0]), dialect='excel')
|
||||
|
||||
first = tuples.next()
|
||||
allowed_headers = set(self.headers.keys())
|
||||
if not set(first) <= allowed_headers:
|
||||
msg = 'Bad headers %s, only those are permitted: %s' % (first, allowed_headers)
|
||||
msg = "Bad headers %s, only those are permitted: %s" % (first, allowed_headers)
|
||||
raise CommandError(msg)
|
||||
|
||||
all_users = []
|
||||
|
@ -109,7 +100,7 @@ class Command(BaseCommand):
|
|||
d = dict(zip(first, line))
|
||||
self.synthesis(d, **options)
|
||||
all_users.append(d)
|
||||
all_profiles = set(functools.reduce(list.__add__, [x['profil'] for x in all_users]))
|
||||
all_profiles = set(reduce(list.__add__, [x['profil'] for x in all_users]))
|
||||
profiles = dict()
|
||||
count_created, count_modified = 0, 0
|
||||
for profile in filter(None, all_profiles):
|
||||
|
@ -131,25 +122,20 @@ class Command(BaseCommand):
|
|||
user_profiles = map(profiles.get, user['profil'])
|
||||
user_instance.mailing_lists = user_profiles
|
||||
if user['groupe']:
|
||||
user_groups = map(
|
||||
lambda x: auth_models.Group.objects.get_or_create(name=x)[0], user['groupe']
|
||||
)
|
||||
user_groups = map(lambda x: auth_models.Group.objects.get_or_create(name=x)[0],
|
||||
user['groupe'])
|
||||
user_instance.groups = user_groups
|
||||
user_instance.is_staff = functools.reduce(
|
||||
bool.__or__, ['Administrateur' in x for x in user['groupe']]
|
||||
)
|
||||
user_instance.is_staff = reduce(bool.__or__, ['Administrateur' in x for x in user['groupe']])
|
||||
if user.get('password'):
|
||||
password = user.get('password')
|
||||
if password.startswith('sha1$'):
|
||||
user_instance.password = password
|
||||
else:
|
||||
user_instance.set_password(user['password'])
|
||||
if options.get('activate'):
|
||||
user_instance.is_active = True
|
||||
user_instance.save()
|
||||
header = ['username', 'prenom', 'nom', 'email', 'password']
|
||||
csv_writer = csv.DictWriter(sys.stdout, header)
|
||||
csv_writer.writerow(dict(zip(header, header)))
|
||||
csv_writer.writerow(dict(zip(header,header)))
|
||||
for user in all_users:
|
||||
d = {}
|
||||
for key in header:
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from ... import notification
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = '<directory>'
|
||||
help = 'Send notifications'
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
notification.process_notifications()
|
|
@ -1,71 +0,0 @@
|
|||
import locale
|
||||
import os.path
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.files import File
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from docbow_project.docbow.models import AttachedFile, Document, FileType, MailingList
|
||||
|
||||
|
||||
def get_object(model, ref, name='name'):
|
||||
'''Try to get a model by id or by name'''
|
||||
if ref.isdigit():
|
||||
return model.objects.get(id=int(ref))
|
||||
else:
|
||||
return model.objects.get(**{name: ref})
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Send a document'''
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('file_tosend', nargs='+', type=str, help='File to send')
|
||||
parser.add_argument('--sender')
|
||||
parser.add_argument('--to-list', action='append')
|
||||
parser.add_argument('--to-user', action='append')
|
||||
parser.add_argument('--filetype')
|
||||
parser.add_argument('--description')
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, **options):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
locale_encoding = locale.nl_langinfo(locale.CODESET)
|
||||
if 'sender' not in options:
|
||||
raise CommandError('missing --sender parameter')
|
||||
try:
|
||||
sender = get_object(User, force_str(options['sender'], locale_encoding), 'username')
|
||||
except User.DoesNotExist:
|
||||
raise CommandError('user %r does not exist' % options['sender'])
|
||||
to_lists = []
|
||||
for name in options.get('to_list') or []:
|
||||
ml = get_object(MailingList, force_str(name, locale_encoding))
|
||||
to_lists.append(ml)
|
||||
to_users = []
|
||||
for username in options.get('to_user') or []:
|
||||
user = get_object(User, force_str(username, locale_encoding), 'username')
|
||||
to_users.append(user)
|
||||
if 'filetype' not in options:
|
||||
raise CommandError('missing --filetype parameter')
|
||||
try:
|
||||
filetype = get_object(FileType, force_str(options['filetype'], locale_encoding))
|
||||
except FileType.DoesNotExist:
|
||||
raise CommandError('filetype %r does not exist' % options['filetype'])
|
||||
if not to_users and not to_lists:
|
||||
print(to_users, to_lists, options)
|
||||
raise CommandError(
|
||||
'you must specify at least one list or user ' 'recipient using --to-list and --to-user.'
|
||||
)
|
||||
document = Document(sender=sender, filetype=filetype)
|
||||
document.save()
|
||||
document.to_user.set(to_users)
|
||||
document.to_list.set(to_lists)
|
||||
for arg in options['file_tosend']:
|
||||
if not os.path.isfile(arg):
|
||||
raise CommandError('file %r does not exist')
|
||||
AttachedFile.objects.create(
|
||||
name=force_str(arg, locale_encoding), content=File(open(arg)), document=document
|
||||
)
|
||||
document.post()
|
|
@ -0,0 +1,169 @@
|
|||
from optparse import make_option
|
||||
import sys
|
||||
import email
|
||||
import email.errors
|
||||
import email.utils
|
||||
import email.header
|
||||
import logging
|
||||
import re
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
import django.contrib.auth.models as auth_models
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import transaction
|
||||
from django.core.exceptions import MultipleObjectsReturned
|
||||
|
||||
from ... import models, views, timestamp
|
||||
from docbow_project.email_utils import u2u_decode
|
||||
|
||||
logger = logging.getLogger('docbow.mail_interface')
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = ''
|
||||
help = '''Convert a mail to a document send.
|
||||
|
||||
In case of failure the following return value is returned:
|
||||
- 1 failure to parse the message on stdin
|
||||
- 2 the mail is missing a subject
|
||||
- 3 the subject could not be decoded
|
||||
- 4 obligatory attributes or contents were missing
|
||||
- 5 notifications mails could not be sent
|
||||
- 6 missing Message-ID
|
||||
'''
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option("--sender"),
|
||||
make_option("--base-url", default="http://localhost:8000"),
|
||||
make_option("--ip", default=''),
|
||||
make_option("--file"))
|
||||
|
||||
def handle(self, *args, **options):
|
||||
mail_sender = None
|
||||
self.logger = logging.LoggerAdapter(logging.getLogger('docbow'), {
|
||||
'ip': options['ip'], 'user': 'Anonymous' })
|
||||
if options.get('sender'):
|
||||
try:
|
||||
mail_sender = auth_models.User.objects.get(username=options['sender'])
|
||||
except auth_models.User.DoesNotExist:
|
||||
raise CommandError('Not found')
|
||||
|
||||
try:
|
||||
if options.get('file'):
|
||||
mail = email.message_from_file(file(options['file']))
|
||||
else:
|
||||
mail = email.message_from_file(sys.stdin)
|
||||
except email.errors.MessageParseError, e:
|
||||
self.error('7.7.1 Error parsing message', exite_code=1)
|
||||
try:
|
||||
self.handle_mail(mail, mail_sender, args, **options)
|
||||
except Exception, e:
|
||||
self.logger.exception('Unknown exception')
|
||||
self.error('7.7.1 Uknown error when handling the mail', exit_code=5)
|
||||
|
||||
def error(self, msg, exit_code=None, *args):
|
||||
if args:
|
||||
print >>sys.stderr, msg % args
|
||||
else:
|
||||
print >>sys.stderr, msg
|
||||
if hasattr(self, 'message_id'):
|
||||
msg = ('smtp interface: message %s refused, ' % self.message_id) + msg
|
||||
else:
|
||||
msg = 'smtp interface: message refused, ' + msg
|
||||
self.logger.error(msg, *args)
|
||||
if exit_code:
|
||||
sys.exit(exit_code)
|
||||
|
||||
def decode_filename(self, filename):
|
||||
'''See if the filename contains encoded-word work around bugs in FileMakerPro'''
|
||||
m = re.match(r'=\?(.*)\?(.*)\?(.*)\?=', filename)
|
||||
if m:
|
||||
result = []
|
||||
for content, encoding in email.header.decode_header(filename):
|
||||
result.append(unicode(content, encoding or 'ascii'))
|
||||
return ''.join(result)
|
||||
else:
|
||||
return filename
|
||||
|
||||
def handle_mail(self, mail, mail_sender, mail_recipients, **options):
|
||||
content_errors = []
|
||||
attachments = []
|
||||
recipients = []
|
||||
description = u''
|
||||
mail_from = email.utils.parseaddr(mail.get('From'))[1]
|
||||
tos = mail.get_all('to', [])
|
||||
ccs = mail.get_all('cc', [])
|
||||
resent_tos = mail.get_all('resent-to', [])
|
||||
resent_ccs = mail.get_all('resent-cc', [])
|
||||
all_recipients = mail_recipients or [b for a,b in email.utils.getaddresses(tos + ccs + resent_tos +
|
||||
resent_ccs)]
|
||||
self.message_id = mail.get('Message-ID', None)
|
||||
if not self.message_id:
|
||||
self.error('7.7.1 Mail is missing a Message-ID', exit_code=6)
|
||||
subject = mail.get('Subject', None)
|
||||
if not subject:
|
||||
self.error('7.7.1 Mail is missing a subject', exit_code=2)
|
||||
try:
|
||||
subject = u2u_decode(subject)
|
||||
except Exception, e:
|
||||
self.error('7.7.1 The subject cannot be decoded', exit_code=3)
|
||||
try:
|
||||
filetype = models.FileType.objects.get(name=subject)
|
||||
except models.FileType.DoesNotExist:
|
||||
content_errors.append('The subject "%s" does not match any known file type' % subject)
|
||||
for part in mail.walk():
|
||||
filename = part.get_filename(None)
|
||||
if part.get_content_type() == 'text/plain' and \
|
||||
('Content-Disposition' not in part or 'inline' in part['Content-Disposition']):
|
||||
charset = part.get_content_charset('us-ascii')
|
||||
description = unicode(part.get_payload(decode=True), charset)
|
||||
|
||||
if filename:
|
||||
attachments.append((self.decode_filename(filename),
|
||||
part.get_payload(decode=True)))
|
||||
for email_address in all_recipients:
|
||||
try:
|
||||
user = auth_models.User.objects.get(email=email_address, delegations_by__isnull=True)
|
||||
recipients.append(user)
|
||||
except auth_models.User.DoesNotExist:
|
||||
msg = 'Recipient %r is not an user of the platform' \
|
||||
% email_address
|
||||
content_errors.append(msg)
|
||||
except MultipleObjectsReturned:
|
||||
msg = 'Recipient %r has more than 1 user in the platform' \
|
||||
% email_address
|
||||
content_errors.append(msg)
|
||||
if not len(attachments):
|
||||
content_errors.append('You must sent at least one attached file')
|
||||
if not len(all_recipients):
|
||||
content_errors.append('You must have at least one recipient in your message.')
|
||||
try:
|
||||
sender = mail_sender or auth_models.User.objects.get(email=mail_from)
|
||||
except auth_models.User.DoesNotExist:
|
||||
content_errors.append('Unable to find an unique sender for the mail %s' % mail.get('From'))
|
||||
if content_errors:
|
||||
msg = [ '7.7.1 The email sent contains many errors:' ]
|
||||
for error in content_errors:
|
||||
msg.append(' - %s' % error)
|
||||
self.error('\n'.join(msg), exit_code=4)
|
||||
else:
|
||||
with transaction.commit_on_success():
|
||||
document = models.Document(sender=sender,
|
||||
comment=description, filetype=filetype)
|
||||
document.save()
|
||||
document.to_user = recipients
|
||||
for filename, payload in attachments:
|
||||
content = ContentFile(payload)
|
||||
attached_file = models.AttachedFile(document=document,
|
||||
name=filename)
|
||||
attached_file.content.save(filename, content, save=False)
|
||||
attached_file.save()
|
||||
document.post()
|
||||
if not views.send_mail_notifications(document, options['base_url'], logger=self.logger):
|
||||
transaction.rollback()
|
||||
self.error('7.7.1 Document send failed because '
|
||||
'some notifications could not be sent, administrators '
|
||||
'have been informed.', code=5)
|
||||
else:
|
||||
blob = document.timestamp_blob()
|
||||
tst = timestamp.timestamp_json(blob)
|
||||
self.logger.info('smtp interface: message %s accepted, sent %s, timestamp %s' % (self.message_id, document, tst))
|
|
@ -0,0 +1,11 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from ... import models
|
||||
|
||||
class Command(BaseCommand):
|
||||
'''Undelete all documents'''
|
||||
help = 'Undelete all documents'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
count = models.Mailbox.objects.filter(deleted=True).count()
|
||||
models.Mailbox.objects.update(deleted=False)
|
||||
print "Undeleted %d documents." % count
|
|
@ -1,8 +1,6 @@
|
|||
import threading
|
||||
import logging
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
__ALL__ = ('KeepUserAroundMiddleware', 'get_extra')
|
||||
|
||||
|
@ -11,22 +9,16 @@ NO_IP = NO_USER
|
|||
|
||||
logger = logging.getLogger('docbow')
|
||||
|
||||
|
||||
def get_extra():
|
||||
"""Extract current remote ip and current user from the thread local storage
|
||||
from the KeepUserAroundMiddleware middleware class.
|
||||
"""
|
||||
k = KeepUserAroundMiddleware
|
||||
return {'ip': k.get_global_ip(), 'user': k.get_global_user()}
|
||||
|
||||
|
||||
class KeepUserAroundMiddleware(MiddlewareMixin):
|
||||
"""
|
||||
Store the request.user and the REMOTE_ADDR in a global thread local
|
||||
variable, so that logging calls inside signals handler can know about
|
||||
them.
|
||||
"""
|
||||
return { 'ip': k.get_global_ip(), 'user': k.get_global_user() }
|
||||
|
||||
class KeepUserAroundMiddleware(object):
|
||||
'''
|
||||
Store the request.user and the REMOTE_ADDR in a global thread local
|
||||
variable, so that logging calls inside signals handler can know about
|
||||
them.
|
||||
'''
|
||||
__middleware_ctx = threading.local()
|
||||
__middleware_ctx.user = NO_USER
|
||||
__middleware_ctx.ip = NO_USER
|
||||
|
@ -42,16 +34,14 @@ class KeepUserAroundMiddleware(MiddlewareMixin):
|
|||
return response
|
||||
|
||||
def process_exception(self, request, exception):
|
||||
logger.error(
|
||||
'Internal Server Error: %s' % request.path,
|
||||
logger.error('Internal Server Error: %s' % request.path,
|
||||
exc_info=sys.exc_info,
|
||||
extra={
|
||||
'status_code': 500,
|
||||
'request': request,
|
||||
'ip': self.get_global_ip(),
|
||||
'user': self.get_global_user(),
|
||||
},
|
||||
)
|
||||
})
|
||||
self.__middleware_ctx.user = NO_USER
|
||||
self.__middleware_ctx.ip = NO_IP
|
||||
return None
|
||||
|
|
|
@ -1,638 +1,239 @@
|
|||
import django.core.validators
|
||||
import django.utils.timezone
|
||||
import picklefield.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
import docbow_project.docbow.models
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Adding model 'FileType'
|
||||
db.create_table('docbow_filetype', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)),
|
||||
))
|
||||
db.send_create_signal('docbow', ['FileType'])
|
||||
|
||||
# Adding model 'Content'
|
||||
db.create_table('docbow_content', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('description', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)),
|
||||
))
|
||||
db.send_create_signal('docbow', ['Content'])
|
||||
|
||||
# Adding model 'Document'
|
||||
db.create_table('docbow_document', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('sender', self.gf('django.db.models.fields.related.ForeignKey')(related_name='documents_sent', to=orm['auth.User'])),
|
||||
('date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
|
||||
('filetype', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['docbow.FileType'])),
|
||||
('comment', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
))
|
||||
db.send_create_signal('docbow', ['Document'])
|
||||
|
||||
# Adding M2M table for field to_user on 'Document'
|
||||
db.create_table('docbow_document_to_user', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('document', models.ForeignKey(orm['docbow.document'], null=False)),
|
||||
('user', models.ForeignKey(orm['auth.user'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_document_to_user', ['document_id', 'user_id'])
|
||||
|
||||
# Adding M2M table for field to_list on 'Document'
|
||||
db.create_table('docbow_document_to_list', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('document', models.ForeignKey(orm['docbow.document'], null=False)),
|
||||
('mailinglist', models.ForeignKey(orm['docbow.mailinglist'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_document_to_list', ['document_id', 'mailinglist_id'])
|
||||
|
||||
# Adding model 'AttachedFile'
|
||||
db.create_table('docbow_attachedfile', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
|
||||
('content', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
|
||||
('document', self.gf('django.db.models.fields.related.ForeignKey')(related_name='attached_files', to=orm['docbow.Document'])),
|
||||
))
|
||||
db.send_create_signal('docbow', ['AttachedFile'])
|
||||
|
||||
# Adding model 'MailingList'
|
||||
db.create_table('docbow_mailinglist', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
|
||||
))
|
||||
db.send_create_signal('docbow', ['MailingList'])
|
||||
|
||||
# Adding M2M table for field members on 'MailingList'
|
||||
db.create_table('docbow_mailinglist_members', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('mailinglist', models.ForeignKey(orm['docbow.mailinglist'], null=False)),
|
||||
('user', models.ForeignKey(orm['auth.user'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_mailinglist_members', ['mailinglist_id', 'user_id'])
|
||||
|
||||
# Adding model 'Mailbox'
|
||||
db.create_table('docbow_mailbox', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('owner', self.gf('django.db.models.fields.related.ForeignKey')(related_name='documents', to=orm['auth.User'])),
|
||||
('document', self.gf('django.db.models.fields.related.ForeignKey')(related_name='mailboxes', to=orm['docbow.Document'])),
|
||||
('seen', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('deleted', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('outbox', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('date', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, auto_now_add=True, blank=True)),
|
||||
))
|
||||
db.send_create_signal('docbow', ['Mailbox'])
|
||||
|
||||
# Adding model 'SendingLimitation'
|
||||
db.create_table('docbow_sendinglimitation', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('mailing_list', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['docbow.MailingList'], unique=True)),
|
||||
))
|
||||
db.send_create_signal('docbow', ['SendingLimitation'])
|
||||
|
||||
# Adding M2M table for field filetypes on 'SendingLimitation'
|
||||
db.create_table('docbow_sendinglimitation_filetypes', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('sendinglimitation', models.ForeignKey(orm['docbow.sendinglimitation'], null=False)),
|
||||
('filetype', models.ForeignKey(orm['docbow.filetype'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_sendinglimitation_filetypes', ['sendinglimitation_id', 'filetype_id'])
|
||||
|
||||
# Adding M2M table for field lists on 'SendingLimitation'
|
||||
db.create_table('docbow_sendinglimitation_lists', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('sendinglimitation', models.ForeignKey(orm['docbow.sendinglimitation'], null=False)),
|
||||
('mailinglist', models.ForeignKey(orm['docbow.mailinglist'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_sendinglimitation_lists', ['sendinglimitation_id', 'mailinglist_id'])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('auth', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
def backwards(self, orm):
|
||||
|
||||
# Deleting model 'FileType'
|
||||
db.delete_table('docbow_filetype')
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AttachedFile',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('name', models.CharField(max_length=300, verbose_name='Name')),
|
||||
(
|
||||
'content',
|
||||
models.FileField(
|
||||
upload_to=docbow_project.docbow.models.generate_filename,
|
||||
max_length=300,
|
||||
verbose_name='File',
|
||||
),
|
||||
),
|
||||
],
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AutomaticForwarding',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Automatic forwarding rule',
|
||||
'verbose_name_plural': 'Automatic forwarding rules',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Content',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('description', models.CharField(unique=True, max_length=128)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['description'],
|
||||
'verbose_name': 'Content',
|
||||
'verbose_name_plural': 'Contents',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Delegation',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
(
|
||||
'by',
|
||||
models.ForeignKey(
|
||||
related_name='delegations_to',
|
||||
verbose_name='From',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
(
|
||||
'to',
|
||||
models.ForeignKey(
|
||||
related_name='delegations_by',
|
||||
verbose_name='To',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ['by'],
|
||||
'db_table': 'auth_delegation',
|
||||
'verbose_name': 'Account delegation',
|
||||
'verbose_name_plural': 'Account delegations',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DeletedDocument',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-document',),
|
||||
'verbose_name': 'deleted document',
|
||||
'verbose_name_plural': 'deleted documents',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DeletedMailbox',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('delegate', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||
],
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DocbowProfile',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('is_guest', models.BooleanField(default=False, verbose_name='Guest user')),
|
||||
(
|
||||
'mobile_phone',
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=32,
|
||||
verbose_name='Mobile phone',
|
||||
validators=[django.core.validators.RegexValidator('^\\+\\d+$')],
|
||||
),
|
||||
),
|
||||
(
|
||||
'personal_email',
|
||||
models.EmailField(
|
||||
help_text='if you provide a personal email address, notifications of new documents will also be sent to this address.',
|
||||
max_length=75,
|
||||
verbose_name='personal email address',
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
'accept_notifications',
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text='If unchecked you will not received notifications anymore, by email or SMS.',
|
||||
verbose_name='Accept to be notified',
|
||||
),
|
||||
),
|
||||
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||
],
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Document',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('real_sender', models.CharField(max_length=64, verbose_name='Real sender', blank=True)),
|
||||
(
|
||||
'date',
|
||||
models.DateTimeField(default=django.utils.timezone.now, verbose_name="Date d'envoi"),
|
||||
),
|
||||
('comment', models.TextField(verbose_name='Comments', blank=True)),
|
||||
('_timestamp', models.TextField(blank=True)),
|
||||
(
|
||||
'private',
|
||||
models.BooleanField(
|
||||
default=False, help_text='delegates cannot see this document', verbose_name='Private'
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'base_manager_name': 'objects',
|
||||
'ordering': ['-date'],
|
||||
'verbose_name': 'Document',
|
||||
'verbose_name_plural': 'Documents',
|
||||
'permissions': (('FORWARD_DOCUMENT', 'Can forward documents'),),
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DocumentForwarded',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('date', models.DateTimeField(auto_now_add=True)),
|
||||
('automatic', models.BooleanField(default=False)),
|
||||
(
|
||||
'from_document',
|
||||
models.ForeignKey(
|
||||
related_name='document_forwarded_to', to='docbow.Document', on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
(
|
||||
'to_document',
|
||||
models.ForeignKey(
|
||||
related_name='document_forwarded_from', to='docbow.Document', on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
],
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FileType',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('name', models.CharField(unique=True, max_length=128)),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='is active')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'verbose_name': 'File type',
|
||||
'verbose_name_plural': 'File types',
|
||||
},
|
||||
bases=(docbow_project.docbow.models.NameNaturalKey, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FileTypeAttachedFileKind',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('name', models.CharField(max_length=128, verbose_name='name')),
|
||||
(
|
||||
'mime_types',
|
||||
models.TextField(
|
||||
help_text='mime types separated by spaces, wildcards are allowed',
|
||||
verbose_name='mime types',
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
'cardinality',
|
||||
models.PositiveSmallIntegerField(
|
||||
default=0,
|
||||
help_text='zero is a special value setting no limitation',
|
||||
verbose_name='cardinality',
|
||||
),
|
||||
),
|
||||
(
|
||||
'minimum',
|
||||
models.PositiveSmallIntegerField(default=0, verbose_name='minimum number of files'),
|
||||
),
|
||||
('position', models.PositiveSmallIntegerField(verbose_name='position')),
|
||||
(
|
||||
'file_type',
|
||||
models.ForeignKey(
|
||||
verbose_name='document type', to='docbow.FileType', on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ('file_type', 'position', 'name'),
|
||||
'verbose_name': 'file type attached file kind',
|
||||
'verbose_name_plural': 'file type attached file kinds',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Mailbox',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('outbox', models.BooleanField(default=False, db_index=True, verbose_name='Outbox message')),
|
||||
('date', models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
'document',
|
||||
models.ForeignKey(
|
||||
related_name='mailboxes',
|
||||
verbose_name='Document',
|
||||
to='docbow.Document',
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
(
|
||||
'owner',
|
||||
models.ForeignKey(
|
||||
related_name='documents',
|
||||
verbose_name='Mailbox owner',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-date'],
|
||||
'verbose_name': 'Mailbox',
|
||||
'verbose_name_plural': 'Mailboxes',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MailingList',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('name', models.CharField(max_length=400, verbose_name='Name')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='is active')),
|
||||
(
|
||||
'mailing_list_members',
|
||||
models.ManyToManyField(
|
||||
related_name='members_lists',
|
||||
verbose_name='Mailing lists members',
|
||||
to='docbow.MailingList',
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
'members',
|
||||
models.ManyToManyField(
|
||||
related_name='mailing_lists',
|
||||
verbose_name='Members',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'verbose_name': 'Mailing list',
|
||||
'verbose_name_plural': 'Mailing lists',
|
||||
},
|
||||
bases=(docbow_project.docbow.models.NameNaturalKey, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Notification',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('create_dt', models.DateTimeField(auto_now_add=True)),
|
||||
('kind', models.CharField(default='new-document', max_length=32)),
|
||||
('done', models.BooleanField(default=False)),
|
||||
('failure', models.TextField(null=True, blank=True)),
|
||||
('ctx', picklefield.fields.PickledObjectField(null=True, editable=False, blank=True)),
|
||||
(
|
||||
'document',
|
||||
models.ForeignKey(blank=True, to='docbow.Document', null=True, on_delete=models.CASCADE),
|
||||
),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-id',),
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NotificationPreference',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('kind', models.CharField(max_length=8, verbose_name='kind')),
|
||||
('value', models.BooleanField(default=True, verbose_name='value')),
|
||||
(
|
||||
'filetype',
|
||||
models.ForeignKey(
|
||||
verbose_name='file type', to='docbow.FileType', on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ('user__last_name', 'user__first_name', 'kind'),
|
||||
'verbose_name': 'notification preference',
|
||||
'verbose_name_plural': 'notification preferences',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SeenDocument',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('document', models.ForeignKey(to='docbow.Document', on_delete=models.CASCADE)),
|
||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-document',),
|
||||
'verbose_name': 'seen document',
|
||||
'verbose_name_plural': 'seen documents',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SendingLimitation',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
(
|
||||
'filetypes',
|
||||
models.ManyToManyField(
|
||||
related_name='filetype_limitation',
|
||||
verbose_name='Limitation des types de fichier',
|
||||
to='docbow.FileType',
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
'lists',
|
||||
models.ManyToManyField(
|
||||
related_name='lists_limitation',
|
||||
verbose_name='Limitation des destinataires',
|
||||
to='docbow.MailingList',
|
||||
),
|
||||
),
|
||||
(
|
||||
'mailing_list',
|
||||
models.OneToOneField(
|
||||
verbose_name='Mailing list', to='docbow.MailingList', on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Limitation par liste de destinataires',
|
||||
'verbose_name_plural': 'Limitation par liste de destinataires',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='filetypeattachedfilekind',
|
||||
unique_together={('name', 'file_type')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='filetype',
|
||||
field=models.ForeignKey(
|
||||
verbose_name='Document type', to='docbow.FileType', on_delete=models.CASCADE
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='reply_to',
|
||||
field=models.ForeignKey(
|
||||
related_name='replies',
|
||||
verbose_name='Reply to',
|
||||
blank=True,
|
||||
to='docbow.Document',
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='sender',
|
||||
field=models.ForeignKey(
|
||||
related_name='documents_sent',
|
||||
verbose_name='Sender',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='to_list',
|
||||
field=models.ManyToManyField(
|
||||
to='docbow.MailingList', verbose_name='Groups to send to', blank=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='to_user',
|
||||
field=models.ManyToManyField(
|
||||
related_name='directly_received_documents',
|
||||
verbose_name='Users to send to',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='deletedmailbox',
|
||||
name='mailbox',
|
||||
field=models.ForeignKey(to='docbow.Mailbox', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='deleteddocument',
|
||||
name='document',
|
||||
field=models.ForeignKey(to='docbow.Document', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='deleteddocument',
|
||||
name='user',
|
||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='delegation',
|
||||
unique_together={('by', 'to')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='automaticforwarding',
|
||||
name='filetypes',
|
||||
field=models.ManyToManyField(
|
||||
related_name='forwarding_rules', verbose_name='filetype', to='docbow.FileType'
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='automaticforwarding',
|
||||
name='forward_to_list',
|
||||
field=models.ManyToManyField(
|
||||
related_name='as_recipient_forwarding_rules',
|
||||
verbose_name='Groups to forward to',
|
||||
to='docbow.MailingList',
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='automaticforwarding',
|
||||
name='forward_to_user',
|
||||
field=models.ManyToManyField(
|
||||
related_name='as_recipient_forwarding_rules',
|
||||
verbose_name='Users to forward to',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='automaticforwarding',
|
||||
name='originaly_to_user',
|
||||
field=models.ManyToManyField(
|
||||
related_name='as_original_recipient_forwarding_rules',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
help_text='At least one recipient must match for the rule to apply.',
|
||||
verbose_name='Original recipients',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='attachedfile',
|
||||
name='document',
|
||||
field=models.ForeignKey(
|
||||
related_name='attached_files',
|
||||
verbose_name='Attached to',
|
||||
to='docbow.Document',
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='attachedfile',
|
||||
name='kind',
|
||||
field=models.ForeignKey(
|
||||
verbose_name='attached file kind',
|
||||
blank=True,
|
||||
to='docbow.FileTypeAttachedFileKind',
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DocbowGroup',
|
||||
fields=[],
|
||||
options={
|
||||
'verbose_name': 'Docbow admin group',
|
||||
'proxy': True,
|
||||
},
|
||||
bases=('auth.group',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DocbowUser',
|
||||
fields=[],
|
||||
options={
|
||||
'verbose_name': 'Docbow admin user',
|
||||
'proxy': True,
|
||||
},
|
||||
bases=('auth.user',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Inbox',
|
||||
fields=[],
|
||||
options={
|
||||
'verbose_name': 'Inbox',
|
||||
'proxy': True,
|
||||
'verbose_name_plural': 'Inboxes',
|
||||
},
|
||||
bases=('docbow.mailbox',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Outbox',
|
||||
fields=[],
|
||||
options={
|
||||
'verbose_name': 'Outbox',
|
||||
'proxy': True,
|
||||
'verbose_name_plural': 'Outboxes',
|
||||
},
|
||||
bases=('docbow.mailbox',),
|
||||
),
|
||||
]
|
||||
# Deleting model 'Content'
|
||||
db.delete_table('docbow_content')
|
||||
|
||||
# Deleting model 'Document'
|
||||
db.delete_table('docbow_document')
|
||||
|
||||
# Removing M2M table for field to_user on 'Document'
|
||||
db.delete_table('docbow_document_to_user')
|
||||
|
||||
# Removing M2M table for field to_list on 'Document'
|
||||
db.delete_table('docbow_document_to_list')
|
||||
|
||||
# Deleting model 'AttachedFile'
|
||||
db.delete_table('docbow_attachedfile')
|
||||
|
||||
# Deleting model 'MailingList'
|
||||
db.delete_table('docbow_mailinglist')
|
||||
|
||||
# Removing M2M table for field members on 'MailingList'
|
||||
db.delete_table('docbow_mailinglist_members')
|
||||
|
||||
# Deleting model 'Mailbox'
|
||||
db.delete_table('docbow_mailbox')
|
||||
|
||||
# Deleting model 'SendingLimitation'
|
||||
db.delete_table('docbow_sendinglimitation')
|
||||
|
||||
# Removing M2M table for field filetypes on 'SendingLimitation'
|
||||
db.delete_table('docbow_sendinglimitation_filetypes')
|
||||
|
||||
# Removing M2M table for field lists on 'SendingLimitation'
|
||||
db.delete_table('docbow_sendinglimitation_lists')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'docbow.attachedfile': {
|
||||
'Meta': {'object_name': 'AttachedFile'},
|
||||
'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attached_files'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.content': {
|
||||
'Meta': {'ordering': "['description']", 'object_name': 'Content'},
|
||||
'description': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.document': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Document'},
|
||||
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'filetype': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sender': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents_sent'", 'to': "orm['auth.User']"}),
|
||||
'to_list': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['docbow.MailingList']", 'null': 'True', 'blank': 'True'}),
|
||||
'to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'directly_received_documents'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'docbow.filetype': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'FileType'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
|
||||
},
|
||||
'docbow.mailbox': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Mailbox'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mailboxes'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'outbox': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'to': "orm['auth.User']"}),
|
||||
'seen': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'docbow.mailinglist': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'MailingList'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'mailing_lists'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.sendinglimitation': {
|
||||
'Meta': {'object_name': 'SendingLimitation'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'filetype_limitation'", 'blank': 'True', 'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'lists': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'lists_limitation'", 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'mailing_list': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['docbow.MailingList']", 'unique': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['docbow']
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('docbow', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='DeletedMailbox',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,123 @@
|
|||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Adding model 'DocumentForwarded'
|
||||
db.create_table('docbow_documentforwarded', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('from_document', self.gf('django.db.models.fields.related.ForeignKey')(related_name='document_forwarded_to', to=orm['docbow.Document'])),
|
||||
('to_document', self.gf('django.db.models.fields.related.ForeignKey')(related_name='document_forwarded_from', to=orm['docbow.Document'])),
|
||||
('date', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, auto_now_add=True, blank=True)),
|
||||
))
|
||||
db.send_create_signal('docbow', ['DocumentForwarded'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Deleting model 'DocumentForwarded'
|
||||
db.delete_table('docbow_documentforwarded')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'docbow.attachedfile': {
|
||||
'Meta': {'object_name': 'AttachedFile'},
|
||||
'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attached_files'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.content': {
|
||||
'Meta': {'ordering': "['description']", 'object_name': 'Content'},
|
||||
'description': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.document': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Document'},
|
||||
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'filetype': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sender': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents_sent'", 'to': "orm['auth.User']"}),
|
||||
'to_list': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['docbow.MailingList']", 'null': 'True', 'blank': 'True'}),
|
||||
'to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'directly_received_documents'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'docbow.documentforwarded': {
|
||||
'Meta': {'object_name': 'DocumentForwarded'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'from_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_to'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'to_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_from'", 'to': "orm['docbow.Document']"})
|
||||
},
|
||||
'docbow.filetype': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'FileType'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
|
||||
},
|
||||
'docbow.mailbox': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Mailbox'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mailboxes'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'outbox': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'to': "orm['auth.User']"}),
|
||||
'seen': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'docbow.mailinglist': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'MailingList'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'mailing_lists'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.sendinglimitation': {
|
||||
'Meta': {'object_name': 'SendingLimitation'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'filetype_limitation'", 'blank': 'True', 'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'lists': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'lists_limitation'", 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'mailing_list': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['docbow.MailingList']", 'unique': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['docbow']
|
|
@ -1,20 +0,0 @@
|
|||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('docbow', '0002_auto_20190711_1812'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='docbowprofile',
|
||||
name='personal_email',
|
||||
field=models.EmailField(
|
||||
help_text='if you provide a personal email address, notifications of new documents will also be sent to this address.',
|
||||
max_length=254,
|
||||
verbose_name='personal email address',
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,172 @@
|
|||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Adding model 'AutomaticForwarding'
|
||||
db.create_table('docbow_automaticforwarding', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
))
|
||||
db.send_create_signal('docbow', ['AutomaticForwarding'])
|
||||
|
||||
# Adding M2M table for field from_list on 'AutomaticForwarding'
|
||||
db.create_table('docbow_automaticforwarding_from_list', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('automaticforwarding', models.ForeignKey(orm['docbow.automaticforwarding'], null=False)),
|
||||
('mailinglist', models.ForeignKey(orm['docbow.mailinglist'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_automaticforwarding_from_list', ['automaticforwarding_id', 'mailinglist_id'])
|
||||
|
||||
# Adding M2M table for field filetypes on 'AutomaticForwarding'
|
||||
db.create_table('docbow_automaticforwarding_filetypes', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('automaticforwarding', models.ForeignKey(orm['docbow.automaticforwarding'], null=False)),
|
||||
('filetype', models.ForeignKey(orm['docbow.filetype'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_automaticforwarding_filetypes', ['automaticforwarding_id', 'filetype_id'])
|
||||
|
||||
# Adding M2M table for field forward_to_user on 'AutomaticForwarding'
|
||||
db.create_table('docbow_automaticforwarding_forward_to_user', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('automaticforwarding', models.ForeignKey(orm['docbow.automaticforwarding'], null=False)),
|
||||
('user', models.ForeignKey(orm['auth.user'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_automaticforwarding_forward_to_user', ['automaticforwarding_id', 'user_id'])
|
||||
|
||||
# Adding M2M table for field forward_to_list on 'AutomaticForwarding'
|
||||
db.create_table('docbow_automaticforwarding_forward_to_list', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('automaticforwarding', models.ForeignKey(orm['docbow.automaticforwarding'], null=False)),
|
||||
('mailinglist', models.ForeignKey(orm['docbow.mailinglist'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_automaticforwarding_forward_to_list', ['automaticforwarding_id', 'mailinglist_id'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Deleting model 'AutomaticForwarding'
|
||||
db.delete_table('docbow_automaticforwarding')
|
||||
|
||||
# Removing M2M table for field from_list on 'AutomaticForwarding'
|
||||
db.delete_table('docbow_automaticforwarding_from_list')
|
||||
|
||||
# Removing M2M table for field filetypes on 'AutomaticForwarding'
|
||||
db.delete_table('docbow_automaticforwarding_filetypes')
|
||||
|
||||
# Removing M2M table for field forward_to_user on 'AutomaticForwarding'
|
||||
db.delete_table('docbow_automaticforwarding_forward_to_user')
|
||||
|
||||
# Removing M2M table for field forward_to_list on 'AutomaticForwarding'
|
||||
db.delete_table('docbow_automaticforwarding_forward_to_list')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'docbow.attachedfile': {
|
||||
'Meta': {'object_name': 'AttachedFile'},
|
||||
'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attached_files'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.automaticforwarding': {
|
||||
'Meta': {'object_name': 'AutomaticForwarding'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'forwarding_rules'", 'symmetrical': 'False', 'to': "orm['docbow.FileType']"}),
|
||||
'forward_to_list': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'forward_to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'from_list': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'forwarding_rules'", 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.content': {
|
||||
'Meta': {'ordering': "['description']", 'object_name': 'Content'},
|
||||
'description': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.document': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Document'},
|
||||
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'filetype': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sender': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents_sent'", 'to': "orm['auth.User']"}),
|
||||
'to_list': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['docbow.MailingList']", 'null': 'True', 'blank': 'True'}),
|
||||
'to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'directly_received_documents'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'docbow.documentforwarded': {
|
||||
'Meta': {'object_name': 'DocumentForwarded'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'from_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_to'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'to_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_from'", 'to': "orm['docbow.Document']"})
|
||||
},
|
||||
'docbow.filetype': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'FileType'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
|
||||
},
|
||||
'docbow.mailbox': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Mailbox'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mailboxes'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'outbox': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'to': "orm['auth.User']"}),
|
||||
'seen': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'docbow.mailinglist': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'MailingList'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'mailing_lists'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.sendinglimitation': {
|
||||
'Meta': {'object_name': 'SendingLimitation'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'filetype_limitation'", 'blank': 'True', 'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'lists': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'lists_limitation'", 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'mailing_list': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['docbow.MailingList']", 'unique': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['docbow']
|
|
@ -0,0 +1,129 @@
|
|||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Removing M2M table for field from_list on 'AutomaticForwarding'
|
||||
db.delete_table('docbow_automaticforwarding_from_list')
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Adding M2M table for field from_list on 'AutomaticForwarding'
|
||||
db.create_table('docbow_automaticforwarding_from_list', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('automaticforwarding', models.ForeignKey(orm['docbow.automaticforwarding'], null=False)),
|
||||
('mailinglist', models.ForeignKey(orm['docbow.mailinglist'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_automaticforwarding_from_list', ['automaticforwarding_id', 'mailinglist_id'])
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'docbow.attachedfile': {
|
||||
'Meta': {'object_name': 'AttachedFile'},
|
||||
'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attached_files'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.automaticforwarding': {
|
||||
'Meta': {'object_name': 'AutomaticForwarding'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'forwarding_rules'", 'symmetrical': 'False', 'to': "orm['docbow.FileType']"}),
|
||||
'forward_to_list': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'forward_to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.content': {
|
||||
'Meta': {'ordering': "['description']", 'object_name': 'Content'},
|
||||
'description': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.document': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Document'},
|
||||
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'filetype': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sender': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents_sent'", 'to': "orm['auth.User']"}),
|
||||
'to_list': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['docbow.MailingList']", 'null': 'True', 'blank': 'True'}),
|
||||
'to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'directly_received_documents'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'docbow.documentforwarded': {
|
||||
'Meta': {'object_name': 'DocumentForwarded'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'from_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_to'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'to_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_from'", 'to': "orm['docbow.Document']"})
|
||||
},
|
||||
'docbow.filetype': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'FileType'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
|
||||
},
|
||||
'docbow.mailbox': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Mailbox'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mailboxes'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'outbox': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'to': "orm['auth.User']"}),
|
||||
'seen': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'docbow.mailinglist': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'MailingList'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'mailing_lists'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.sendinglimitation': {
|
||||
'Meta': {'object_name': 'SendingLimitation'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'filetype_limitation'", 'blank': 'True', 'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'lists': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'lists_limitation'", 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'mailing_list': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['docbow.MailingList']", 'unique': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['docbow']
|
|
@ -1,15 +0,0 @@
|
|||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('docbow', '0003_auto_20200319_1129'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='docbowprofile',
|
||||
name='external_id',
|
||||
field=models.CharField(max_length=256, verbose_name='External identifer', blank=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,125 @@
|
|||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Adding field 'AutomaticForwarding.automatic'
|
||||
db.add_column('docbow_automaticforwarding', 'automatic', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Deleting field 'AutomaticForwarding.automatic'
|
||||
db.delete_column('docbow_automaticforwarding', 'automatic')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'docbow.attachedfile': {
|
||||
'Meta': {'object_name': 'AttachedFile'},
|
||||
'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attached_files'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.automaticforwarding': {
|
||||
'Meta': {'object_name': 'AutomaticForwarding'},
|
||||
'automatic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'forwarding_rules'", 'symmetrical': 'False', 'to': "orm['docbow.FileType']"}),
|
||||
'forward_to_list': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'forward_to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.content': {
|
||||
'Meta': {'ordering': "['description']", 'object_name': 'Content'},
|
||||
'description': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.document': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Document'},
|
||||
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'filetype': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sender': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents_sent'", 'to': "orm['auth.User']"}),
|
||||
'to_list': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['docbow.MailingList']", 'null': 'True', 'blank': 'True'}),
|
||||
'to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'directly_received_documents'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'docbow.documentforwarded': {
|
||||
'Meta': {'object_name': 'DocumentForwarded'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'from_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_to'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'to_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_from'", 'to': "orm['docbow.Document']"})
|
||||
},
|
||||
'docbow.filetype': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'FileType'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
|
||||
},
|
||||
'docbow.mailbox': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Mailbox'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mailboxes'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'outbox': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'to': "orm['auth.User']"}),
|
||||
'seen': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'docbow.mailinglist': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'MailingList'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'mailing_lists'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.sendinglimitation': {
|
||||
'Meta': {'object_name': 'SendingLimitation'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'filetype_limitation'", 'blank': 'True', 'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'lists': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'lists_limitation'", 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'mailing_list': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['docbow.MailingList']", 'unique': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['docbow']
|
|
@ -1,22 +0,0 @@
|
|||
# Generated by Django 1.11.29 on 2020-07-21 12:30
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('docbow', '0004_external_identifier'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='deleteddocument',
|
||||
name='soft_delete',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='deleteddocument',
|
||||
name='soft_delete_date',
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,131 @@
|
|||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Adding field 'DocumentForwarded.automatic'
|
||||
db.add_column('docbow_documentforwarded', 'automatic', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
|
||||
|
||||
# Deleting field 'AutomaticForwarding.automatic'
|
||||
db.delete_column('docbow_automaticforwarding', 'automatic')
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Deleting field 'DocumentForwarded.automatic'
|
||||
db.delete_column('docbow_documentforwarded', 'automatic')
|
||||
|
||||
# Adding field 'AutomaticForwarding.automatic'
|
||||
db.add_column('docbow_automaticforwarding', 'automatic', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'docbow.attachedfile': {
|
||||
'Meta': {'object_name': 'AttachedFile'},
|
||||
'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attached_files'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.automaticforwarding': {
|
||||
'Meta': {'object_name': 'AutomaticForwarding'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'forwarding_rules'", 'symmetrical': 'False', 'to': "orm['docbow.FileType']"}),
|
||||
'forward_to_list': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'forward_to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.content': {
|
||||
'Meta': {'ordering': "['description']", 'object_name': 'Content'},
|
||||
'description': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.document': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Document'},
|
||||
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'filetype': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sender': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents_sent'", 'to': "orm['auth.User']"}),
|
||||
'to_list': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['docbow.MailingList']", 'null': 'True', 'blank': 'True'}),
|
||||
'to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'directly_received_documents'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'docbow.documentforwarded': {
|
||||
'Meta': {'object_name': 'DocumentForwarded'},
|
||||
'automatic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'from_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_to'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'to_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_from'", 'to': "orm['docbow.Document']"})
|
||||
},
|
||||
'docbow.filetype': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'FileType'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
|
||||
},
|
||||
'docbow.mailbox': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Mailbox'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mailboxes'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'outbox': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'to': "orm['auth.User']"}),
|
||||
'seen': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'docbow.mailinglist': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'MailingList'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'mailing_lists'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.sendinglimitation': {
|
||||
'Meta': {'object_name': 'SendingLimitation'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'filetype_limitation'", 'blank': 'True', 'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'lists': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'lists_limitation'", 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'mailing_list': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['docbow.MailingList']", 'unique': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['docbow']
|
|
@ -1,19 +0,0 @@
|
|||
# Generated by Django 1.11.29 on 2020-12-10 14:33
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('docbow', '0005_soft_delete'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='extra_senders',
|
||||
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name='Extra senders'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,131 @@
|
|||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Adding M2M table for field originaly_to_user on 'AutomaticForwarding'
|
||||
db.create_table('docbow_automaticforwarding_originaly_to_user', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('automaticforwarding', models.ForeignKey(orm['docbow.automaticforwarding'], null=False)),
|
||||
('user', models.ForeignKey(orm['auth.user'], null=False))
|
||||
))
|
||||
db.create_unique('docbow_automaticforwarding_originaly_to_user', ['automaticforwarding_id', 'user_id'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Removing M2M table for field originaly_to_user on 'AutomaticForwarding'
|
||||
db.delete_table('docbow_automaticforwarding_originaly_to_user')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'docbow.attachedfile': {
|
||||
'Meta': {'object_name': 'AttachedFile'},
|
||||
'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attached_files'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.automaticforwarding': {
|
||||
'Meta': {'object_name': 'AutomaticForwarding'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'forwarding_rules'", 'symmetrical': 'False', 'to': "orm['docbow.FileType']"}),
|
||||
'forward_to_list': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'forward_to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'originaly_to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'as_original_recipient_forwarding_rules'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'docbow.content': {
|
||||
'Meta': {'ordering': "['description']", 'object_name': 'Content'},
|
||||
'description': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'docbow.document': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Document'},
|
||||
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'filetype': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sender': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents_sent'", 'to': "orm['auth.User']"}),
|
||||
'to_list': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['docbow.MailingList']", 'null': 'True', 'blank': 'True'}),
|
||||
'to_user': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'directly_received_documents'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'docbow.documentforwarded': {
|
||||
'Meta': {'object_name': 'DocumentForwarded'},
|
||||
'automatic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'from_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_to'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'to_document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'document_forwarded_from'", 'to': "orm['docbow.Document']"})
|
||||
},
|
||||
'docbow.filetype': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'FileType'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
|
||||
},
|
||||
'docbow.mailbox': {
|
||||
'Meta': {'ordering': "['-date']", 'object_name': 'Mailbox'},
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mailboxes'", 'to': "orm['docbow.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'outbox': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'to': "orm['auth.User']"}),
|
||||
'seen': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'docbow.mailinglist': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'MailingList'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'mailing_lists'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'docbow.sendinglimitation': {
|
||||
'Meta': {'object_name': 'SendingLimitation'},
|
||||
'filetypes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'filetype_limitation'", 'blank': 'True', 'to': "orm['docbow.FileType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'lists': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'lists_limitation'", 'symmetrical': 'False', 'to': "orm['docbow.MailingList']"}),
|
||||
'mailing_list': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['docbow.MailingList']", 'unique': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['docbow']
|
|
@ -1,17 +0,0 @@
|
|||
# Generated by Django 1.11.29 on 2021-02-24 12:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('docbow', '0006_extra_senders'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='filetype',
|
||||
name='extra_senders',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='Extra senders'),
|
||||
),
|
||||
]
|
|
@ -1,17 +0,0 @@
|
|||
# Generated by Django 3.2.16 on 2023-06-13 11:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('docbow', '0007_filetype_extra_senders'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mailinglist',
|
||||
name='name',
|
||||
field=models.CharField(max_length=1000, verbose_name='Name'),
|
||||
),
|
||||
]
|
File diff suppressed because it is too large
Load Diff
|
@ -1,206 +0,0 @@
|
|||
import importlib
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.template.loader import TemplateDoesNotExist, render_to_string
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_journal import journal as django_journal
|
||||
|
||||
from docbow_project.docbow import app_settings, models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseNotifier:
|
||||
def __init__(self):
|
||||
# accumulate preferences of users first
|
||||
self.filter = set()
|
||||
for np in models.NotificationPreference.objects.filter(kind=self.key, value=False):
|
||||
self.filter.add((np.user.id, np.filetype.id))
|
||||
|
||||
def skip(self, notification):
|
||||
'''Verify if notification should be skipped'''
|
||||
if notification.document and notification.user:
|
||||
if (notification.user.id, notification.document.filetype.id) in self.filter:
|
||||
return True
|
||||
return False
|
||||
|
||||
def generate_part(self, template_path, notification):
|
||||
template_path = template_path.format(kind=notification.kind)
|
||||
try:
|
||||
ctx = {
|
||||
'notification': notification,
|
||||
'settings': settings,
|
||||
}
|
||||
if isinstance(notification.ctx, dict):
|
||||
ctx.update(notification.ctx)
|
||||
return render_to_string(template_path, ctx)
|
||||
except TemplateDoesNotExist:
|
||||
return None
|
||||
|
||||
def process(sef, notification):
|
||||
pass
|
||||
|
||||
def finish(self):
|
||||
pass
|
||||
|
||||
|
||||
class MailNotifier(BaseNotifier):
|
||||
description = _('Email')
|
||||
key = 'email'
|
||||
subject_template = 'docbow/email-notification_{kind}_subject.txt'
|
||||
body_template = 'docbow/email-notification_{kind}_body.txt'
|
||||
html_body_template = 'docbow/email-notification_{kind}_body.html'
|
||||
|
||||
def process(self, notification):
|
||||
emails = []
|
||||
if notification.user:
|
||||
user = notification.user
|
||||
if user.email:
|
||||
emails.append(user.email)
|
||||
if app_settings.PERSONAL_EMAIL:
|
||||
try:
|
||||
personal_email = user.docbowprofile.personal_email
|
||||
if personal_email:
|
||||
emails.append(personal_email)
|
||||
except models.DocbowProfile.DoesNotExist:
|
||||
pass
|
||||
if not emails and notification.ctx and 'to' in notification.ctx:
|
||||
emails.extend(notification.ctx['to'])
|
||||
emails = [email for email in emails if email]
|
||||
if not emails:
|
||||
return
|
||||
headers = {}
|
||||
if notification.ctx and 'reply_to' in notification.ctx:
|
||||
headers['Reply-To'] = notification.ctx['reply_to']
|
||||
to = set(emails)
|
||||
subject = self.generate_part(self.subject_template, notification)
|
||||
subject = subject.replace('\n', '').replace('\r', '')
|
||||
body = self.generate_part(self.body_template, notification)
|
||||
html_body = self.generate_part(self.html_body_template, notification)
|
||||
mail = EmailMultiAlternatives(to=list(to), subject=subject, body=body, headers=headers)
|
||||
if html_body:
|
||||
mail.attach_alternative(html_body, 'text/html')
|
||||
mail.send(fail_silently=False)
|
||||
django_journal.record(
|
||||
'mail-notify',
|
||||
'mail notification {notification} ' 'sent for document {document} to {to} of user {recipient}',
|
||||
notification=notification,
|
||||
recipient=notification.user,
|
||||
document=notification.document,
|
||||
to=','.join(to),
|
||||
)
|
||||
|
||||
|
||||
class SMSNotifier(BaseNotifier):
|
||||
"""Variable in sms notification:
|
||||
- document
|
||||
- settings
|
||||
"""
|
||||
|
||||
description = _('SMS')
|
||||
key = 'sms'
|
||||
body_template = 'docbow/sms-notification_{kind}_body.txt'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.mobile_phones = dict()
|
||||
|
||||
def process(self, notification):
|
||||
if not app_settings.MOBILE_PHONE:
|
||||
return
|
||||
try:
|
||||
profile = models.DocbowProfile.objects.get(user=notification.user)
|
||||
except models.DocbowProfile.DoesNotExist:
|
||||
return
|
||||
if not profile.mobile_phone:
|
||||
return
|
||||
self.mobile_phones.setdefault((notification.document, notification.kind), []).append(
|
||||
(notification.user, profile.mobile_phone)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_carrier(cls):
|
||||
return resolve_class(settings.DOCBOW_SMS_CARRIER_CLASS)()
|
||||
|
||||
def finish(self):
|
||||
if not self.mobile_phones:
|
||||
return
|
||||
exc = None
|
||||
for key, value in self.mobile_phones.items():
|
||||
try:
|
||||
document, kind = key
|
||||
notification = models.Notification(document=document, kind=kind)
|
||||
body = self.generate_part(self.body_template, notification)
|
||||
sms_carrier = self.get_carrier()
|
||||
for user, phone_number in value:
|
||||
django_journal.record(
|
||||
'sms-notify',
|
||||
'sms notification for document {document} to user '
|
||||
'{recipient} with phone number {phone_number}',
|
||||
document=document,
|
||||
recipient=user,
|
||||
phone_number=phone_number,
|
||||
)
|
||||
sms_carrier.send_sms([v[1] for v in value], body)
|
||||
except Exception as e:
|
||||
exc = e
|
||||
if exc:
|
||||
raise exc
|
||||
|
||||
|
||||
def resolve_class(class_path):
|
||||
module, cls_name = class_path.rsplit('.', 1)
|
||||
module = importlib.import_module(module)
|
||||
return getattr(module, cls_name)
|
||||
|
||||
|
||||
def get_notifiers():
|
||||
notification_classes = getattr(
|
||||
settings, 'DOCBOW_NOTIFIERS', ['docbow_project.docbow.notification.MailNotifier']
|
||||
)
|
||||
return [resolve_class(class_path)() for class_path in notification_classes]
|
||||
|
||||
|
||||
def process_notifications():
|
||||
notifiers = get_notifiers()
|
||||
for notification in models.Notification.objects.order_by('id').select_for_update().filter(done=False):
|
||||
for notifier in notifiers:
|
||||
failures = []
|
||||
if not notifier.skip(notification):
|
||||
try:
|
||||
notifier.process(notification)
|
||||
except Exception as e:
|
||||
failures.append(
|
||||
'Exception %r when handling with notifier %r' % (force_str(e), notifier.__class__)
|
||||
)
|
||||
logger.exception(
|
||||
'Exception when handling notification %r with notifier %r', notification, notifier
|
||||
)
|
||||
django_journal.error_record(
|
||||
'error',
|
||||
'notification {notification} failed for ' 'notifier {notifier}',
|
||||
notification=notification,
|
||||
notifier=notifier.__class__,
|
||||
)
|
||||
notification.done = True
|
||||
if len(failures) > 0:
|
||||
notification.failure = ','.join(failures)
|
||||
notification.save()
|
||||
for notifier in notifiers:
|
||||
try:
|
||||
notifier.finish()
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
'Exception when finishing handling ' 'notification %r with notifier %r',
|
||||
notification,
|
||||
notifier,
|
||||
)
|
||||
django_journal.error_record(
|
||||
'error',
|
||||
'unable to finish sending ' 'notification with notifier {notifier}, error: {error}',
|
||||
notifier=notifier.__class__,
|
||||
error=force_str(e),
|
||||
)
|
|
@ -1,130 +0,0 @@
|
|||
#
|
||||
# w.c.s. - web application for online forms
|
||||
# Copyright (C) 2005-2013 Entr'ouvert
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import zipfile
|
||||
|
||||
try:
|
||||
import elementtree.ElementTree as ET
|
||||
except ImportError:
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
except ImportError:
|
||||
ET = None
|
||||
|
||||
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
OFFICE_NS = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
|
||||
TABLE_NS = 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'
|
||||
TEXT_NS = 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'
|
||||
XLINK_NS = 'http://www.w3.org/1999/xlink'
|
||||
|
||||
|
||||
class Workbook:
|
||||
def __init__(self, encoding='utf-8'):
|
||||
self.sheets = []
|
||||
self.encoding = encoding
|
||||
|
||||
def add_sheet(self, name):
|
||||
sheet = WorkSheet(self, name)
|
||||
self.sheets.append(sheet)
|
||||
return sheet
|
||||
|
||||
def get_node(self):
|
||||
root = ET.Element('{%s}document-content' % OFFICE_NS)
|
||||
ET.SubElement(root, '{%s}scripts' % OFFICE_NS)
|
||||
ET.SubElement(root, '{%s}font-face-decls' % OFFICE_NS)
|
||||
body = ET.SubElement(root, '{%s}body' % OFFICE_NS)
|
||||
spreadsheet = ET.SubElement(body, '{%s}spreadsheet' % OFFICE_NS)
|
||||
for sheet in self.sheets:
|
||||
spreadsheet.append(sheet.get_node())
|
||||
return root
|
||||
|
||||
def get_data(self):
|
||||
return ET.tostring(self.get_node(), 'utf-8')
|
||||
|
||||
def save(self, output):
|
||||
z = zipfile.ZipFile(output, 'w')
|
||||
z.writestr('content.xml', self.get_data())
|
||||
z.writestr('mimetype', 'application/vnd.oasis.opendocument.spreadsheet')
|
||||
z.writestr(
|
||||
'META-INF/manifest.xml',
|
||||
'''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
|
||||
<manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>
|
||||
<manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
|
||||
<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
|
||||
<manifest:file-entry manifest:full-path="META-INF/manifest.xml" manifest:media-type="text/xml"/>
|
||||
<manifest:file-entry manifest:full-path="mimetype" manifest:media-type="text/plain"/>
|
||||
</manifest:manifest>''',
|
||||
)
|
||||
z.writestr(
|
||||
'styles.xml',
|
||||
'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0">
|
||||
</office:document-styles>''',
|
||||
)
|
||||
z.close()
|
||||
|
||||
|
||||
class WorkSheet:
|
||||
def __init__(self, workbook, name):
|
||||
self.cells = {}
|
||||
self.name = name
|
||||
self.workbook = workbook
|
||||
|
||||
def write(self, row, column, value, hint=None):
|
||||
if row not in self.cells:
|
||||
self.cells[row] = {}
|
||||
self.cells[row][column] = WorkCell(self, value, hint=hint)
|
||||
|
||||
def get_node(self):
|
||||
root = ET.Element('{%s}table' % TABLE_NS)
|
||||
root.attrib['{%s}name' % TABLE_NS] = self.name
|
||||
ET.SubElement(root, '{%s}table-column' % TABLE_NS)
|
||||
for i in range(0, max(self.cells.keys()) + 1):
|
||||
row = ET.SubElement(root, '{%s}table-row' % TABLE_NS)
|
||||
for j in range(0, max(self.cells.get(i).keys()) + 1):
|
||||
cell = self.cells.get(i, {}).get(j, None)
|
||||
if not cell:
|
||||
ET.SubElement(row, '{%s}table-cell' % TABLE_NS)
|
||||
else:
|
||||
row.append(cell.get_node())
|
||||
return root
|
||||
|
||||
|
||||
class WorkCell:
|
||||
def __init__(self, worksheet, value, hint=None):
|
||||
if not isinstance(value, str):
|
||||
value = force_str(value, 'utf-8')
|
||||
self.value = value
|
||||
self.worksheet = worksheet
|
||||
self.hint = hint
|
||||
|
||||
def get_node(self):
|
||||
root = ET.Element('{%s}table-cell' % TABLE_NS)
|
||||
root.attrib['{%s}value-type' % OFFICE_NS] = 'string'
|
||||
p = ET.SubElement(root, '{%s}p' % TEXT_NS)
|
||||
if self.hint == 'uri':
|
||||
base_filename = self.value.split('/')[-1]
|
||||
if base_filename:
|
||||
a = ET.SubElement(p, '{%s}a' % TEXT_NS)
|
||||
a.attrib['{%s}href' % XLINK_NS] = self.value
|
||||
a.text = base_filename
|
||||
return root
|
||||
p.text = self.value
|
||||
return root
|
|
@ -1,340 +0,0 @@
|
|||
import urllib.parse
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.transaction import atomic
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic.base import TemplateResponseMixin, View
|
||||
from django.views.generic.edit import FormView, UpdateView
|
||||
from django_journal.models import Journal
|
||||
|
||||
from docbow_project.docbow import app_settings, cbv, forms, models, utils
|
||||
|
||||
|
||||
class ProfileView(cbv.FormWithRequestMixin, cbv.FormWithPostTarget, UpdateView):
|
||||
form_class = forms.ProfileForm
|
||||
template_name = 'docbow/profile.html'
|
||||
prefix = 'profile'
|
||||
success_url = '#profile'
|
||||
|
||||
def get_object(self):
|
||||
try:
|
||||
return models.DocbowProfile.objects.get(user=self.request.user)
|
||||
except models.DocbowProfile.DoesNotExist:
|
||||
return None
|
||||
|
||||
def is_post_target(self):
|
||||
return 'profile-validate' in self.request.POST
|
||||
|
||||
def form_valid(self, form):
|
||||
self.request.record('update-profile', 'modified its profile', **form.cleaned_data)
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DelegateView(cbv.FormWithPostTarget, FormView):
|
||||
form_class = forms.DelegationForm
|
||||
template_name = 'docbow/delegate.html'
|
||||
success_url = '#delegate'
|
||||
prefix = 'delegate'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def add_journal_to_delegations(self, delegations):
|
||||
delegations__to = [delegation.to for delegation in delegations]
|
||||
journals = (
|
||||
Journal.objects.for_objects(delegations__to)
|
||||
.filter(tag__name='login')
|
||||
.order_by('-time')[: len(delegations__to) * 5]
|
||||
)
|
||||
journal_by_users = dict()
|
||||
for journal in journals:
|
||||
for objectdata in journal.objectdata_set.all():
|
||||
if objectdata.tag.name == 'delegate':
|
||||
journal_by_users.setdefault(objectdata.object_id, []).append(journal.time)
|
||||
for delegation in delegations:
|
||||
delegation.journals = journal_by_users.get(delegation.to.id, [])[:5]
|
||||
|
||||
@property
|
||||
def delegations(self):
|
||||
qs = models.Delegation.objects.all()
|
||||
qs = qs.filter(by=self.request.user)
|
||||
qs = qs.select_related()
|
||||
qs = qs.prefetch_related('to__docbowprofile')
|
||||
self.add_journal_to_delegations(qs)
|
||||
return qs
|
||||
|
||||
def get_form_kwargs(self, **kwargs):
|
||||
kwargs = super().get_form_kwargs(**kwargs)
|
||||
kwargs['user'] = self.request.user
|
||||
kwargs['delegatees'] = [delegation.to for delegation in self.delegations]
|
||||
kwargs['request'] = self.request
|
||||
return kwargs
|
||||
|
||||
def delete(self, delegation):
|
||||
request = self.request
|
||||
delegation.delete()
|
||||
delegate_user = delegation.to
|
||||
request.record(
|
||||
'delete-delegation',
|
||||
'deleted delegation ' '{delegation} to user {delegated}',
|
||||
delegation=delegation.id,
|
||||
delegated=delegate_user,
|
||||
)
|
||||
user = request.user
|
||||
# notify delegate
|
||||
ctx = {
|
||||
'user': utils.clean_ldap_user(user),
|
||||
'delegate': delegate_user,
|
||||
'to': [delegate_user.email],
|
||||
'reply_to': user.email,
|
||||
}
|
||||
|
||||
models.Notification.objects.create(kind='delete-delegation', ctx=ctx)
|
||||
messages.info(request, _('Delegation %s supressed') % delegate_user)
|
||||
messages.info(request, _('%s has been notified.') % delegate_user.email)
|
||||
request.record(
|
||||
'notify', 'notified {email} of ' 'the deletion of its delegate user', email=delegate_user.email
|
||||
)
|
||||
|
||||
# delete guest accounts
|
||||
if delegation.guest_delegate:
|
||||
if 'mellon' in app_settings.settings.INSTALLED_APPS and delegate_user.saml_identifiers.count():
|
||||
err, json_data, err_desc = utils.a2_wscall(
|
||||
urllib.parse.urljoin(
|
||||
app_settings.settings.AUTHENTIC_URL,
|
||||
'api/users/%s' % delegate_user.saml_identifiers.first().name_id,
|
||||
),
|
||||
'delete',
|
||||
)
|
||||
|
||||
delegate_user.delete()
|
||||
request.record(
|
||||
'delete-delegate',
|
||||
'deleted delegate ' 'user {username}, {first_name} {last_name}, ' '({email})',
|
||||
username=delegate_user.username,
|
||||
first_name=delegate_user.first_name,
|
||||
last_name=delegate_user.last_name,
|
||||
email=delegate_user.email,
|
||||
)
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
def is_post_target(self):
|
||||
if 'delegate-create' in self.request.POST:
|
||||
return True
|
||||
for delegation in self.delegations:
|
||||
username = delegation.to.username
|
||||
if f'delegate-delete-{username}.x' in self.request.POST:
|
||||
return True
|
||||
return False
|
||||
|
||||
@atomic
|
||||
def post(self, request, *args, **kwargs):
|
||||
if 'delegate-create' in request.POST:
|
||||
return super().post(request, *args, **kwargs)
|
||||
for delegation in self.delegations:
|
||||
username = delegation.to.username
|
||||
if f'delegate-delete-{username}.x' in self.request.POST:
|
||||
return self.delete(delegation)
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
request = self.request
|
||||
is_guest = not form.cleaned_data.get('existing_user')
|
||||
ctx = {
|
||||
'user': utils.clean_ldap_user(request.user),
|
||||
'reply_to': request.user.email,
|
||||
'is_guest': is_guest,
|
||||
}
|
||||
if is_guest:
|
||||
delegate_username = utils.get_free_delegation_number(request.user)
|
||||
delegate_user = User(
|
||||
username=delegate_username,
|
||||
first_name=form.cleaned_data.get('first_name', ''),
|
||||
last_name=form.cleaned_data.get('last_name', ''),
|
||||
email=form.cleaned_data.get('email', ''),
|
||||
)
|
||||
delegate_user.set_unusable_password()
|
||||
delegate_user.save()
|
||||
models.DocbowProfile.objects.create(
|
||||
user=delegate_user,
|
||||
is_guest=True,
|
||||
accept_notifications=app_settings.DEFAULT_ACCEPT_NOTIFICATIONS_FOR_GUEST,
|
||||
)
|
||||
delegation, created = models.Delegation.objects.get_or_create(by=request.user, to=delegate_user)
|
||||
if 'mellon' in app_settings.settings.INSTALLED_APPS:
|
||||
import mellon
|
||||
|
||||
ctx['sso'] = True
|
||||
issuer = mellon.models.Issuer.objects.filter(
|
||||
entity_id__startswith=app_settings.settings.AUTHENTIC_URL
|
||||
).first()
|
||||
if not issuer:
|
||||
raise ImproperlyConfigured('Mellon issuer not found')
|
||||
|
||||
mellon.models.UserSAMLIdentifier.objects.create(
|
||||
name_id=form.cleaned_data['name_id'],
|
||||
issuer=issuer,
|
||||
user=delegate_user,
|
||||
)
|
||||
|
||||
request.record(
|
||||
'create-delegate',
|
||||
'created delegate with ' 'parameters {first_name} {last_name} <{email}>',
|
||||
**form.cleaned_data,
|
||||
)
|
||||
ctx.update(
|
||||
{
|
||||
'password_reset_link': utils.make_password_reset_url(request, delegate_user),
|
||||
'to': [form.cleaned_data['email']],
|
||||
'delegate': delegate_user,
|
||||
'delegation': delegation,
|
||||
}
|
||||
)
|
||||
messages.info(request, _('New delegation to user %s created.') % delegate_user.username)
|
||||
messages.info(
|
||||
request, _('A notification was sent to %s about this new delegation') % delegate_user.email
|
||||
)
|
||||
else:
|
||||
delegate_user = form.cleaned_data.get('existing_user')
|
||||
delegate_username = delegate_user.username
|
||||
delegation, created = models.Delegation.objects.get_or_create(by=request.user, to=delegate_user)
|
||||
ctx.update(
|
||||
{
|
||||
'to': models.all_emails(delegate_user),
|
||||
'delegate': delegate_user,
|
||||
'delegation': delegation,
|
||||
}
|
||||
)
|
||||
messages.info(request, _('New delegation to user %s created.') % delegate_user.username)
|
||||
messages.info(
|
||||
request,
|
||||
_('A notification was sent to %s about this new delegation') % delegate_user.get_full_name(),
|
||||
)
|
||||
models.Notification.objects.create(kind='new-delegation', ctx=ctx)
|
||||
request.record(
|
||||
'create-delegation', 'created delegation to ' 'user {delegated}', delegated=delegate_user
|
||||
)
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['delegations'] = self.delegations
|
||||
ctx['received_delegations'] = self.request.user.delegations_by.all()
|
||||
ctx['delegate_to_existing_user'] = app_settings.DELEGATE_TO_EXISTING_USER
|
||||
return ctx
|
||||
|
||||
|
||||
class PasswordChangeView(cbv.FormWithPostTarget, FormView):
|
||||
template_name = 'registration/password_change_form.html'
|
||||
form_class = forms.PasswordChangeFormWithLogging
|
||||
prefix = 'password-change'
|
||||
success_url = '#password-change'
|
||||
|
||||
def is_post_target(self):
|
||||
return 'password-change-validate' in self.request.POST
|
||||
|
||||
def form_valid(self, form):
|
||||
messages.info(self.request, _('Password changed'))
|
||||
form.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_form_kwargs(self, **kwargs):
|
||||
kwargs = super().get_form_kwargs(**kwargs)
|
||||
kwargs['user'] = self.request.user
|
||||
return kwargs
|
||||
|
||||
|
||||
class EmailView(cbv.FormWithPostTarget, UpdateView):
|
||||
form_class = forms.EmailForm
|
||||
template_name = 'docbow/email.html'
|
||||
prefix = 'email'
|
||||
success_url = '#email'
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
def form_valid(self, form):
|
||||
messages.info(self.request, _('Email changed'))
|
||||
self.request.record('update-email', 'modified its email', **form.cleaned_data)
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class NotificationPreferenceView(cbv.FormWithPostTarget, cbv.FormWithRequestMixin, FormView):
|
||||
form_class = forms.NotificationPreferencesForm
|
||||
template_name = 'docbow/notifications.html'
|
||||
success_url = '#notifications'
|
||||
prefix = 'notifications'
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save()
|
||||
messages.info(self.request, _('Notification preferences saved'))
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['mobile_phone'] = app_settings.MOBILE_PHONE
|
||||
ctx['mobile_phone_paragraph'] = _(
|
||||
'If You would like to receive a SMS alert each '
|
||||
'time your inbox receives a document, provide your '
|
||||
'mobile phone number. If you do not fill this field '
|
||||
'you won\'t receive any SMS alert'
|
||||
)
|
||||
return ctx
|
||||
|
||||
|
||||
class FullProfileView(TemplateResponseMixin, View):
|
||||
# multiplex all profile views
|
||||
template_name = 'docbow/full-profile.html'
|
||||
subviews = (
|
||||
('notifications_form', NotificationPreferenceView),
|
||||
('delegate_form', DelegateView),
|
||||
)
|
||||
if 'mellon' not in app_settings.settings.INSTALLED_APPS:
|
||||
subviews = subviews + (('password_change_form', PasswordChangeView),)
|
||||
if app_settings.MOBILE_PHONE or app_settings.PERSONAL_EMAIL:
|
||||
subviews = (('profile_form', ProfileView),) + subviews
|
||||
if app_settings.EDIT_EMAIL and 'mellon' not in app_settings.settings.INSTALLED_APPS:
|
||||
subviews += (('email_form', EmailView),)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if models.is_guest(request.user):
|
||||
self.subviews = filter(lambda s: s[0] != 'delegate_form', self.subviews)
|
||||
print(self.subviews)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
ctx = {}
|
||||
for var_name, view_class in self.subviews:
|
||||
res = self.get_view(var_name, view_class)
|
||||
if isinstance(res, dict):
|
||||
ctx.update(res)
|
||||
else:
|
||||
return res
|
||||
return self.render_to_response(ctx)
|
||||
|
||||
def get_view(self, var_name, view_class):
|
||||
view = view_class()
|
||||
view.request, view.args, view.kwargs = self.request, self.args, self.kwargs
|
||||
if hasattr(view, 'get_object'):
|
||||
view.object = view.get_object()
|
||||
if self.request.method == 'POST' and view.is_post_target():
|
||||
res = view.post(self.request, *self.args, **self.kwargs)
|
||||
if isinstance(res, HttpResponseRedirect):
|
||||
return res
|
||||
else:
|
||||
ctx = res.context_data
|
||||
ctx[var_name] = ctx.pop('form')
|
||||
return ctx
|
||||
else:
|
||||
form_class = view.get_form_class()
|
||||
form = view.get_form(form_class)
|
||||
return view.get_context_data(**{var_name: form})
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
ctx = {}
|
||||
for var_name, view_class in self.subviews:
|
||||
ctx.update(self.get_view(var_name, view_class))
|
||||
return self.render_to_response(ctx)
|
|
@ -5,17 +5,17 @@
|
|||
# http://jtauber.com/
|
||||
|
||||
# Copyright (c) 2006 James Tauber
|
||||
#
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
|
@ -50,6 +50,7 @@ import os.path
|
|||
|
||||
|
||||
class Trie:
|
||||
|
||||
def __init__(self):
|
||||
self.root = [None, {}]
|
||||
|
||||
|
@ -71,43 +72,46 @@ class Trie:
|
|||
|
||||
|
||||
class Collator:
|
||||
|
||||
def __init__(self, filename):
|
||||
|
||||
self.table = Trie()
|
||||
self.load(filename)
|
||||
|
||||
def load(self, filename):
|
||||
for line in open(filename):
|
||||
if line.startswith('#') or line.startswith('%'):
|
||||
if line.startswith("#") or line.startswith("%"):
|
||||
continue
|
||||
if line.strip() == '':
|
||||
if line.strip() == "":
|
||||
continue
|
||||
line = line[: line.find('#')] + '\n'
|
||||
line = line[: line.find('%')] + '\n'
|
||||
line = line[:line.find("#")] + "\n"
|
||||
line = line[:line.find("%")] + "\n"
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('@'):
|
||||
|
||||
if line.startswith("@"):
|
||||
pass
|
||||
else:
|
||||
semicolon = line.find(';')
|
||||
semicolon = line.find(";")
|
||||
charList = line[:semicolon].strip().split()
|
||||
x = line[semicolon:]
|
||||
collElements = []
|
||||
while True:
|
||||
begin = x.find('[')
|
||||
begin = x.find("[")
|
||||
if begin == -1:
|
||||
break
|
||||
end = x[begin:].find(']')
|
||||
collElement = x[begin : begin + end + 1]
|
||||
x = x[begin + 1 :]
|
||||
|
||||
break
|
||||
end = x[begin:].find("]")
|
||||
collElement = x[begin:begin+end+1]
|
||||
x = x[begin + 1:]
|
||||
|
||||
alt = collElement[1]
|
||||
chars = collElement[2:-1].split('.')
|
||||
|
||||
chars = collElement[2:-1].split(".")
|
||||
|
||||
collElements.append((alt, chars))
|
||||
integer_points = [int(ch, 16) for ch in charList]
|
||||
self.table.add(integer_points, collElements)
|
||||
|
||||
def sort_key(self, string):
|
||||
|
||||
collation_elements = []
|
||||
|
||||
lookup_key = [ord(ch) for ch in string]
|
||||
|
@ -115,20 +119,19 @@ class Collator:
|
|||
value, lookup_key = self.table.find_prefix(lookup_key)
|
||||
if not value:
|
||||
# @@@
|
||||
raise ValueError(map(hex, lookup_key))
|
||||
raise ValueError, map(hex, lookup_key)
|
||||
collation_elements.extend(value)
|
||||
|
||||
|
||||
sort_key = []
|
||||
|
||||
|
||||
for level in range(4):
|
||||
if level:
|
||||
sort_key.append(0) # level separator
|
||||
sort_key.append(0) # level separator
|
||||
for element in collation_elements:
|
||||
ce_l = int(element[1][level], 16)
|
||||
if ce_l:
|
||||
sort_key.append(ce_l)
|
||||
|
||||
|
||||
return tuple(sort_key)
|
||||
|
||||
|
||||
collator = Collator(os.path.join(os.path.dirname(__file__), 'allkeys.txt'))
|
||||
|
|
|
@ -1,103 +1,58 @@
|
|||
from django.contrib.auth import models as auth_models
|
||||
from django.contrib.auth.models import User
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||
from django.db.models.signals import m2m_changed
|
||||
from django.db.models.signals import post_save as db_post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext_noop as N_
|
||||
from django_journal import journal as django_journal
|
||||
from django.db.models.signals import post_save as db_post_save, m2m_changed
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from docbow_project.docbow import middleware, models
|
||||
import models
|
||||
from logger_adapter import DjangoLoggerAdapter
|
||||
from middleware import get_extra
|
||||
|
||||
db_logger = logging.getLogger('docbow.db')
|
||||
auth_logger = logging.getLogger('docbow.auth')
|
||||
|
||||
def logged_in_handler(sender, request, user, **kwargs):
|
||||
extra = middleware.get_extra()
|
||||
kwargs = {}
|
||||
msg = '{user} logged in'
|
||||
if hasattr(user, 'delegate'):
|
||||
kwargs['delegate'] = user.delegate
|
||||
msg = '{delegate} logged in'
|
||||
if hasattr(user, 'user'):
|
||||
user = user.user
|
||||
django_journal.record('login', msg, user=user, ip=extra['ip'], **kwargs)
|
||||
|
||||
global auth_logger
|
||||
new_logger = DjangoLoggerAdapter(auth_logger, request)
|
||||
new_logger.info(_('logged in'))
|
||||
|
||||
def logged_out_handler(sender, request, user, **kwargs):
|
||||
extra = middleware.get_extra()
|
||||
msg = '{user} logged out'
|
||||
if hasattr(user, 'delegate'):
|
||||
kwargs['delegate'] = user.delegate
|
||||
msg = '{delegate} logged out'
|
||||
if hasattr(user, 'user'):
|
||||
user = user.user
|
||||
django_journal.record('logout', msg, user=user, ip=extra['ip'], **kwargs)
|
||||
|
||||
|
||||
do_log = True
|
||||
|
||||
global auth_logger
|
||||
new_logger = DjangoLoggerAdapter(auth_logger, request)
|
||||
new_logger.info(_('logged out'))
|
||||
|
||||
def modified_data(sender, instance, created, raw, using, **kwargs):
|
||||
global do_log
|
||||
if using != 'default':
|
||||
return
|
||||
extra = middleware.get_extra()
|
||||
if not do_log:
|
||||
return
|
||||
if hasattr(extra['user'], 'is_anonymous') and extra['user'].is_anonymous:
|
||||
extra['user'] = '-'
|
||||
if extra['user'] == middleware.NO_USER:
|
||||
return
|
||||
global db_logger
|
||||
if created:
|
||||
tag = 'create'
|
||||
msg = '{user} created {model} {instance}'
|
||||
msg = _('created %(model)s %(name)s')
|
||||
else:
|
||||
tag = 'modify'
|
||||
msg = '{user} modified {model} {instance}'
|
||||
django_journal.record(
|
||||
tag, msg, user=extra['user'], ip=extra['ip'], model=sender._meta.model_name, instance=instance
|
||||
)
|
||||
|
||||
msg = _('modified %(model)s %(name)s')
|
||||
extra = get_extra()
|
||||
if hasattr(extra['user'], 'is_anonymous') and extra['user'].is_anonymous():
|
||||
return
|
||||
db_logger.info(msg, dict(model=unicode(sender._meta.verbose_name),
|
||||
name=instance), extra=extra)
|
||||
|
||||
def list_m2m_changed_handler(sender, instance, action, reverse, model, pk_set, using, **kwargs):
|
||||
if using != 'default':
|
||||
return
|
||||
if action.startswith('pre'):
|
||||
return
|
||||
action.replace('_', '-')
|
||||
extra = middleware.get_extra()
|
||||
if hasattr(extra['user'], 'is_anonymous') and extra['user'].is_anonymous:
|
||||
extra['user'] = '-'
|
||||
if extra['user'] == middleware.NO_USER:
|
||||
return
|
||||
users = model.objects.filter(pk__in=pk_set or [])
|
||||
if action == 'post_clear':
|
||||
msg = N_('cleared mailing list {list}')
|
||||
users = [None]
|
||||
elif action == 'post_add':
|
||||
msg = N_('added user {member} to mailing list {list}')
|
||||
global db_logger
|
||||
msg = None
|
||||
if action == 'post_add':
|
||||
msg = _('added user %(user)s to list %(list)s')
|
||||
elif action == 'post_remove':
|
||||
msg = N_('removed user {member} from mailing list {list}')
|
||||
for user in users:
|
||||
django_journal.record(action, msg, list=instance, member=user, ip=extra['ip'], user=extra['user'])
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=User.groups.through)
|
||||
def groups_changed(sender, instance, action, **kwargs):
|
||||
'''When a user get a group, give it access to the administration.'''
|
||||
global do_log
|
||||
if action.startswith('post'):
|
||||
try:
|
||||
do_log = False
|
||||
if not instance.is_superuser:
|
||||
instance.is_staff = instance.groups.exists()
|
||||
instance.save()
|
||||
finally:
|
||||
do_log = True
|
||||
|
||||
msg = _('removed user %(user)s from list %(list)s')
|
||||
elif action == 'post_clear':
|
||||
msg = _('cleared list %(user)s%(list)s')
|
||||
if msg:
|
||||
db_logger.info(msg,
|
||||
dict(user=', '.join(map(unicode, model.objects.filter(pk__in=pk_set or []))),
|
||||
list=unicode(instance)), extra=get_extra())
|
||||
|
||||
user_logged_in.connect(logged_in_handler)
|
||||
user_logged_out.connect(logged_out_handler)
|
||||
db_post_save.connect(modified_data, sender=models.MailingList)
|
||||
db_post_save.connect(modified_data, sender=auth_models.User)
|
||||
db_post_save.connect(modified_data, sender=auth_models.Group)
|
||||
db_post_save.connect(modified_data, sender=models.DocbowUser)
|
||||
db_post_save.connect(modified_data, sender=models.DocbowGroup)
|
||||
m2m_changed.connect(list_m2m_changed_handler, sender=models.MailingList.members.through)
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
import json
|
||||
import logging
|
||||
from urllib.request import urlopen
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.http import urlencode
|
||||
from django_journal import journal as django_journal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OVHSMSCarrier:
|
||||
URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi'
|
||||
SMS_CLASS = 1
|
||||
|
||||
def send_sms(self, to, message, sms_class=None, no_stop=True):
|
||||
payload = force_str(message).encode('utf-8')
|
||||
sms_class = sms_class or self.SMS_CLASS
|
||||
to = ','.join([t.replace('+', '00') for t in to])
|
||||
params = {
|
||||
'account': settings.OVH_SMS_ACCOUNT,
|
||||
'login': settings.OVH_SMS_LOGIN,
|
||||
'password': settings.OVH_SMS_PASSWORD,
|
||||
'from': settings.OVH_SMS_FROM,
|
||||
'to': to,
|
||||
'message': payload,
|
||||
'contentType': 'text/json',
|
||||
'class': sms_class,
|
||||
}
|
||||
if no_stop:
|
||||
params['no_stop'] = 1
|
||||
django_journal.error_record(
|
||||
'ovh-sms', 'OVH SMS CARRIER: sending message {message} to {numbers}', message=message, numbers=to
|
||||
)
|
||||
stream = urlopen('%s?%s' % (self.URL, urlencode(params)))
|
||||
result = json.loads(stream.read())
|
||||
if 100 <= result['status'] < 200:
|
||||
credit_alert = getattr(settings, 'OVH_SMS_CREDIT_ALERT', 100)
|
||||
credit_left = result['creditLeft']
|
||||
if credit_left < credit_alert:
|
||||
django_journal.error_record(
|
||||
'error',
|
||||
'OVH SMS CARRIER: credit ' 'left({credit_left}) < credit alert limit({credit_alert})',
|
||||
credit_left=credit_left,
|
||||
credit_alert=credit_alert,
|
||||
)
|
||||
else:
|
||||
django_journal.error_record(
|
||||
'error', 'OVH SMS CARRIER: status "{status}"' 'message "{message}"', **result
|
||||
)
|
|
@ -1,104 +0,0 @@
|
|||
from django.db import connection
|
||||
|
||||
|
||||
def get_sql(sql, params):
|
||||
"""
|
||||
Execute an SQL query and return the associated cursor.
|
||||
"""
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(sql, params)
|
||||
return cursor
|
||||
|
||||
|
||||
def get_sql_count(sql, params):
|
||||
"""
|
||||
Execute a count on SUB-SELECT and return the count.
|
||||
"""
|
||||
cursor = get_sql('''SELECT COUNT(*) FROM (%s) AS CNT''' % sql, params)
|
||||
return cursor.fetchone()[0]
|
||||
|
||||
|
||||
def get_sql_ids(sql, params):
|
||||
"""
|
||||
Retrieve a list of numerical ids.
|
||||
"""
|
||||
cursor = get_sql(sql, params)
|
||||
return (row[0] for row in cursor.fetchall())
|
||||
|
||||
|
||||
def get_complex_join(qs, sql, params):
|
||||
ids = list(get_sql_ids(sql, params))
|
||||
return qs.filter(pk__in=ids)
|
||||
|
||||
|
||||
def get_unseen_documents_count(related_users, user):
|
||||
query = GET_UNSEEN_DOCUMENTS_SQL % ('(%s)' % ', '.join(['%s'] * len(related_users)))
|
||||
return get_sql_count(
|
||||
query,
|
||||
(False,)
|
||||
+ tuple(related_users.values_list('id', flat=True))
|
||||
+ (user.pk, user.pk, False, user.pk, True),
|
||||
)
|
||||
|
||||
|
||||
def get_documents(qs, related_users, user, outbox):
|
||||
query = GET_DOCUMENTS_SQL % ('(%s)' % ', '.join(['%s'] * len(related_users)))
|
||||
qs = get_complex_join(
|
||||
qs,
|
||||
query,
|
||||
(outbox,) + tuple(related_users.values_list('id', flat=True)) + (user.pk, False, user.pk, True),
|
||||
)
|
||||
qs = qs.prefetch_related('to_list', 'to_user', 'mailboxes__owner')
|
||||
qs = qs.extra(select={'seen': SEEN_DOCUMENT % user.pk})
|
||||
return qs
|
||||
|
||||
|
||||
def get_trash_documents(qs, related_users, user, outbox):
|
||||
query = GET_TRASH_DOCUMENTS_SQL % ('(%s)' % ', '.join(['%s'] * len(related_users)))
|
||||
qs = get_complex_join(
|
||||
qs,
|
||||
query,
|
||||
(outbox,) + tuple(related_users.values_list('id', flat=True)) + (user.pk, False, user.pk, True),
|
||||
)
|
||||
qs = qs.prefetch_related('to_list', 'to_user', 'mailboxes__owner')
|
||||
return qs
|
||||
|
||||
|
||||
GET_UNSEEN_DOCUMENTS_SQL = '''SELECT d.id
|
||||
FROM docbow_document AS d
|
||||
INNER JOIN docbow_mailbox AS mb
|
||||
ON mb.outbox = %%s AND mb.document_id = d.id AND mb.owner_id IN %s
|
||||
WHERE
|
||||
NOT EXISTS(SELECT 1 FROM docbow_deleteddocument dd WHERE dd.document_id = d.id AND dd.user_id = %%s)
|
||||
AND NOT EXISTS(SELECT 1 FROM docbow_seendocument sd WHERE sd.document_id = d.id AND sd.user_id = %%s)
|
||||
AND (d.private = %%s OR (mb.owner_id = %%s AND d.private = %%s))
|
||||
GROUP BY d.id, d.date
|
||||
ORDER BY d.date
|
||||
'''
|
||||
|
||||
GET_DOCUMENTS_SQL = '''SELECT d.id
|
||||
FROM docbow_document AS d
|
||||
INNER JOIN docbow_mailbox AS mb ON
|
||||
mb.outbox = %%s AND mb.document_id = d.id AND mb.owner_id IN %s
|
||||
WHERE
|
||||
NOT EXISTS(SELECT 1 FROM docbow_deleteddocument dd WHERE dd.document_id = d.id AND dd.user_id = %%s)
|
||||
AND (d.private = %%s OR (mb.owner_id = %%s AND d.private = %%s))
|
||||
GROUP BY d.id, d.date
|
||||
ORDER BY d.date'''
|
||||
|
||||
|
||||
GET_TRASH_DOCUMENTS_SQL = '''SELECT d.id
|
||||
FROM docbow_document AS d
|
||||
INNER JOIN docbow_mailbox AS mb ON
|
||||
mb.outbox = %%s AND mb.document_id = d.id AND mb.owner_id IN %s
|
||||
LEFT JOIN docbow_deleteddocument AS dd ON
|
||||
dd.document_id = d.id AND dd.user_id = %%s AND dd.soft_delete
|
||||
WHERE (d.private = %%s OR (mb.owner_id = %%s AND d.private = %%s))
|
||||
GROUP BY d.id, d.date
|
||||
ORDER BY d.date'''
|
||||
|
||||
|
||||
SEEN_DOCUMENT = '''SELECT COUNT(*) > 0
|
||||
FROM docbow_seendocument
|
||||
WHERE docbow_seendocument.document_id = docbow_document.id
|
||||
AND docbow_seendocument.user_id = %s'''
|
|
@ -0,0 +1,100 @@
|
|||
@charset 'UTF-8';
|
||||
/*
|
||||
* jQuery File Upload UI Plugin CSS 5.0.6
|
||||
* https://github.com/blueimp/jQuery-File-Upload
|
||||
*
|
||||
* Copyright 2010, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://creativecommons.org/licenses/MIT/
|
||||
*/
|
||||
|
||||
.fileupload-buttonbar .ui-button input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
border: solid transparent;
|
||||
border-width: 0 0 100px 200px;
|
||||
opacity: 0;
|
||||
filter: alpha(opacity=0);
|
||||
-o-transform: translate(250px, -50px) scale(1);
|
||||
-moz-transform: translate(-300px, 0) scale(4);
|
||||
direction: ltr;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fileinput-button {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Fix for IE 6: */
|
||||
*html .fileinput-button {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
/* Fix for IE 7: */
|
||||
*+html .fileinput-button {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.fileupload-buttonbar {
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
|
||||
.fileupload-buttonbar .ui-button {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.fileupload-content {
|
||||
padding: 0.2em 0.4em;
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
.fileupload-content .ui-progressbar {
|
||||
width: 200px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.fileupload-content .ui-progressbar-value {
|
||||
background: url(../images/pbar-ani.gif);
|
||||
}
|
||||
|
||||
.fileupload-content .fileupload-progressbar {
|
||||
width: 400px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.files {
|
||||
margin: 10px 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.files td {
|
||||
padding: 5px;
|
||||
border-spacing: 5px;
|
||||
}
|
||||
|
||||
.files img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.files .name {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.files .size {
|
||||
padding: 0 10px 0 0;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ui-state-disabled .ui-state-disabled {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
|
||||
.ui-state-disabled input {
|
||||
cursor: default;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
div#send-file-form .ctrlHolder > label {
|
||||
margin-top: 2em;
|
||||
font-size: 1em;
|
||||
background: #00458A url(images/titre_menu_fl_compo.gif) no-repeat left bottom;
|
||||
background: #2E5A65 url(../images/titre_menu_fl_compo.gif) no-repeat left bottom;
|
||||
height: 40px;
|
||||
font-weight: bold;
|
||||
color: white;
|
|
@ -3,31 +3,25 @@
|
|||
body {
|
||||
font-family: sans-serif;
|
||||
margin: 5px auto;
|
||||
max-width: 1600px;
|
||||
max-width: 200ex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00458A;
|
||||
}
|
||||
|
||||
#left-column {
|
||||
width: 18%;
|
||||
color: #2E5A65;
|
||||
}
|
||||
|
||||
#main-column {
|
||||
width: 77.5%;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
#main-column h3 {
|
||||
background: #00458A;
|
||||
background: #2E5A65;
|
||||
color: white;
|
||||
padding: 2px 1ex;
|
||||
font-size: 100%;
|
||||
margin-top: 0px;
|
||||
font-size: 1em;
|
||||
background: #00458A url(images/titre_menu_fl_compo.gif) no-repeat left bottom;
|
||||
background: #2E5A65 url(../images/titre_menu_fl_compo.gif) no-repeat left bottom;
|
||||
height: 40px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
|
@ -46,7 +40,7 @@ div.forward-form {
|
|||
}
|
||||
|
||||
ul.messages {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
width: 30em;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
|
@ -96,14 +90,14 @@ div.unauth-box {
|
|||
|
||||
#header h1,
|
||||
div.unauth-box h1 {
|
||||
color: #222;
|
||||
background: #2E5A65 url(../images/banner_pw.png) center left no-repeat;
|
||||
color: white;
|
||||
margin: 0;
|
||||
font-size: 100%;
|
||||
height: 80px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: left;
|
||||
padding-left: 60px;
|
||||
text-indent: -9999px;
|
||||
padding-left: 130px;
|
||||
}
|
||||
|
||||
div.unauth-box p {
|
||||
|
@ -205,7 +199,7 @@ div.unauth-box #forgotten-password {
|
|||
/* header */
|
||||
#header {
|
||||
position: relative;
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#top-infos {
|
||||
|
@ -213,13 +207,13 @@ div.unauth-box #forgotten-password {
|
|||
top: -6px;
|
||||
right: 1ex;
|
||||
text-align: right;
|
||||
color: black;
|
||||
color: white;
|
||||
font-size: 80%;
|
||||
line-height: 134%;
|
||||
}
|
||||
|
||||
#top-infos a {
|
||||
color: black;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -229,7 +223,7 @@ div.unauth-box #forgotten-password {
|
|||
}
|
||||
/* left column */
|
||||
#left-column {
|
||||
background: white url(images/fond_menus.jpg) bottom left repeat-x;
|
||||
background: white url(../images/fond_menus.jpg) bottom left repeat-x;
|
||||
border: 1px solid #d4d4d2;
|
||||
}
|
||||
|
||||
|
@ -242,11 +236,7 @@ div.unauth-box #forgotten-password {
|
|||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
padding-right: 5px;
|
||||
background: #00458A url(images/titre_menu_fl_compo.gif) no-repeat left bottom;
|
||||
}
|
||||
|
||||
#left-column h3#welcome {
|
||||
height: 19px;
|
||||
background: #2E5A65 url(../images/titre_menu_fl_compo.gif) no-repeat left bottom;
|
||||
}
|
||||
|
||||
#left-column .menu {
|
||||
|
@ -255,37 +245,39 @@ div.unauth-box #forgotten-password {
|
|||
|
||||
#left-column .menu a {
|
||||
display: block;
|
||||
line-height: 3em;
|
||||
height: 3em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#left-column li#menu-inbox,
|
||||
#left-column li#menu-send-file-selector,
|
||||
#left-column li#menu-send-file,
|
||||
#left-column li#menu-outbox {
|
||||
padding: 10px 10px;
|
||||
padding: 2px 10px;
|
||||
border: none;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
background: #00458A;
|
||||
background: #417d8c;
|
||||
}
|
||||
|
||||
#left-column li#menu-send-file-selector {
|
||||
#left-column li#menu-send-file {
|
||||
}
|
||||
|
||||
#left-column li#menu-inbox a,
|
||||
#left-column li#menu-send-file-selector a,
|
||||
#left-column li#menu-send-file a,
|
||||
#left-column li#menu-outbox a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#left-column li.current#menu-inbox,
|
||||
#left-column li.current#menu-send-file-selector,
|
||||
#left-column li.current#menu-send-file,
|
||||
#left-column li.current#menu-outbox {
|
||||
margin-right: -22px;
|
||||
background: #00458A url(images/fleche.gif) center right no-repeat;
|
||||
background: #417d8c url(../images/fleche.gif) center right no-repeat;
|
||||
-webkit-border-radius: 3px 0 0 3px;
|
||||
-moz-border-radius: 3px 0 0 3px;
|
||||
border-radius: 3px 0 0 3px;
|
||||
|
@ -303,8 +295,6 @@ li#menu-auth_password_change {
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
#left-column li#menu-mailing-lists a,
|
||||
#left-column li#menu-profile a,
|
||||
#left-column li#menu-docbow_admin-index a,
|
||||
#left-column li#menu-contact a,
|
||||
#left-column li#menu-auth_password_change a,
|
||||
|
@ -330,11 +320,102 @@ li#menu-auth_password_change {
|
|||
span.hits-first, span.hits-last, span.hits {
|
||||
font-weight: bold;
|
||||
}
|
||||
/* file listing */
|
||||
.file-listing .paginator {
|
||||
text-align: right;
|
||||
font-size: smaller;
|
||||
}
|
||||
.file-listing table {
|
||||
width: 100%;
|
||||
border-spacing: 0px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.file-listing .row .new a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.file-listing td.new_header {
|
||||
width: 20px;
|
||||
font-size: 3em;
|
||||
line-height: 0px;
|
||||
color: #2E5A65;
|
||||
}
|
||||
|
||||
.file-listing .sender{
|
||||
width: 20%;
|
||||
text-align: left;
|
||||
}
|
||||
.file-listing .new_header {
|
||||
width: 0%;
|
||||
}
|
||||
.file-listing .date {
|
||||
width: 20%;
|
||||
text-align: right;
|
||||
}
|
||||
.file-listing .header {
|
||||
background: #2E5A65;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.file-listing a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.file-listing tr.new a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
tbody tr.even {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.file-listing tr.comment td {
|
||||
font-size: 80%;
|
||||
position: relative;
|
||||
left: 1%;
|
||||
}
|
||||
.file-listing .row {
|
||||
border: 1ex solid transparent;
|
||||
padding: 1ex;
|
||||
}
|
||||
.file-listing table {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.file-listing tbody .row a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.file-listing td.quoi {
|
||||
width: 38%;
|
||||
}
|
||||
|
||||
.file-listing td.a-qui {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.file-listing td.quand {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.file-listing td.delete_column {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.file-listing tr.forward {
|
||||
color: black;
|
||||
background: lightgreen;
|
||||
}
|
||||
.file-listing td > a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* detail page */
|
||||
|
||||
#download-buttons {
|
||||
float: right;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
@ -351,11 +432,6 @@ span.hits-first, span.hits-last, span.hits {
|
|||
border-radius: 7px;
|
||||
}
|
||||
|
||||
#zip-download {
|
||||
background: #00458A;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.download-button a {
|
||||
text-decoration: none;
|
||||
padding: 1ex 3ex;
|
||||
|
@ -405,14 +481,6 @@ span.hits-first, span.hits-last, span.hits {
|
|||
width: 96%;
|
||||
}
|
||||
|
||||
#id_not_before, #id_not_after {
|
||||
width: 10ex;
|
||||
}
|
||||
|
||||
#id_subject, #id_message {
|
||||
width: 80ex;
|
||||
}
|
||||
|
||||
/* forms */
|
||||
|
||||
#main-column label {
|
||||
|
@ -430,13 +498,6 @@ span.hits-first, span.hits-last, span.hits {
|
|||
margin-left: 15px;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=email],
|
||||
input[type=password],
|
||||
textarea {
|
||||
width: 30ex;
|
||||
}
|
||||
|
||||
div#errorMsg {
|
||||
background: #FF6754;
|
||||
border: 2px solid red;
|
||||
|
@ -462,12 +523,19 @@ div.buttonHolder {
|
|||
}
|
||||
|
||||
#delegations {
|
||||
border-collapse: collapse;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
margin: 1em 0 2em 0;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
background: #2E5A65;
|
||||
color: white;
|
||||
padding: 5px 3px 2px;
|
||||
border-right: 2px solid white;
|
||||
}
|
||||
|
||||
#delegations th#firstname,
|
||||
#delegations th#lastname {
|
||||
width: 17%;
|
||||
|
@ -506,194 +574,3 @@ div.table-of-contents ol {
|
|||
div.table-of-contents ol ol {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
/* delegations */
|
||||
tr.last-connection td {
|
||||
font-size: smaller;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* mailing-lists display */
|
||||
#mailing-lists-index {
|
||||
float: right;
|
||||
width: 50%;
|
||||
background: #f8f8f8;
|
||||
padding-top: 10px;
|
||||
}
|
||||
#mailing-lists-index li {
|
||||
display: block;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
#mailbox-table .delete form input {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
#mailbox-table table {
|
||||
width: 100%;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#mailbox-table td.seen span.true {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
#mailbox-table td.seen span.false {
|
||||
background-image: url(images/emblem-new.png);
|
||||
min-height: 16px;
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
#mailbox-table th.date, #mailbox-table td.date {
|
||||
width: 10em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#mailbox-table td {
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
#mailbox-table td.recipients {
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
#mailbox-table tbody {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
#mailbox-table tbody tr:hover {
|
||||
background-color: lightgrey;
|
||||
color: #00458A;
|
||||
}
|
||||
|
||||
td.filenames {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.formHint {
|
||||
font-style: italic;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
#div_id_profile-accept_notifications label {
|
||||
display: inline;
|
||||
}
|
||||
.ctrlHolder {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* upload form */
|
||||
.template-download .name {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#footer {
|
||||
color: lightgrey;
|
||||
margin: 2em;
|
||||
}
|
||||
|
||||
.profile-form, form.uniForm {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* overlay styles */
|
||||
.overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
background-color: rgba(100, 100, 100, 0.5);
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.overlay > * {
|
||||
margin: 5%;
|
||||
height: 70%;
|
||||
background: white;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
#notifications {
|
||||
width: 50%;
|
||||
margin: 5% auto;
|
||||
}
|
||||
|
||||
.notifications-choice {
|
||||
text-align: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.notifications-label {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.overlay:target {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#notifications-table {
|
||||
overflow: auto;
|
||||
max-height: 400px;
|
||||
max-width: 400px;
|
||||
margin: auto;
|
||||
display: block;
|
||||
line-height: 1em;
|
||||
margin: 10px auto;
|
||||
padding: 3px;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
|
||||
#notifications tbody tr:nth-child(even) {
|
||||
background-color: #d8d8d8;
|
||||
}
|
||||
|
||||
.actions-form {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Tooltips in mailbox table */
|
||||
.tooltip {
|
||||
position: relative;
|
||||
float: right;
|
||||
}
|
||||
#mailbox-table .plus {
|
||||
font-style: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
#mailbox-table .tooltip p {
|
||||
z-index: 100;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
display: none;
|
||||
top: 50%;
|
||||
width: 80ex;
|
||||
left: 100%;
|
||||
border: 1px solid black;
|
||||
font-style: normal;
|
||||
color: black;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
p#filter-inputs {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
p#filter-inputs input {
|
||||
flex: 0 0 auto;
|
||||
margin: 0 0.5em 0 0.5em;
|
||||
}
|
||||
|
||||
p#filter-inputs .action {
|
||||
flex: 1 0 auto;
|
||||
text-align: right;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
.table-container th.asc:after {
|
||||
content: '\0000a0\0025b2';
|
||||
float: right;
|
||||
}
|
||||
.table-container th.desc:after {
|
||||
content: '\0000a0\0025bc';
|
||||
float: right;
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
table.paleblue {
|
||||
border-collapse: collapse;
|
||||
border-color: #CCC;
|
||||
border: 1px solid #DDD;
|
||||
}
|
||||
|
||||
table.paleblue,
|
||||
table.paleblue + ul.pagination {
|
||||
font: normal 11px/14px 'Lucida Grande', Verdana, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
table.paleblue a:link,
|
||||
table.paleblue a:visited,
|
||||
table.paleblue + ul.pagination > li > a {
|
||||
color: #5B80B2;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.paleblue a:hover {
|
||||
color: #036;
|
||||
}
|
||||
|
||||
table.paleblue td,
|
||||
table.paleblue th {
|
||||
padding: 5px;
|
||||
line-height: 13px;
|
||||
border-bottom: 1px solid #EEE;
|
||||
border-left: 1px solid #DDD;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table.paleblue thead th:first-child,
|
||||
table.paleblue thead td:first-child {
|
||||
border-left: none !important;
|
||||
}
|
||||
|
||||
table.paleblue thead th,
|
||||
table.paleblue thead td {
|
||||
background: #FCFCFC url(../img/header-bg.png) left bottom repeat-x;
|
||||
border-bottom: 1px solid #DDD;
|
||||
padding: 2px 5px;
|
||||
font-size: 11px;
|
||||
vertical-align: middle;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
table.paleblue thead th > a:link,
|
||||
table.paleblue thead th > a:visited {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
table.paleblue thead th.orderable > a {
|
||||
padding-right: 20px;
|
||||
background: url(../img/arrow-inactive-up.png) right center no-repeat;
|
||||
}
|
||||
table.paleblue thead th.orderable.asc > a {
|
||||
background-image: url(../img/arrow-active-up.png);
|
||||
}
|
||||
table.paleblue thead th.orderable.desc > a {
|
||||
background-image: url(../img/arrow-active-down.png);
|
||||
}
|
||||
|
||||
table.paleblue tr.odd {
|
||||
background-color: #EDF3FE;
|
||||
}
|
||||
|
||||
table.paleblue tr.even {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
table.paleblue + ul.pagination {
|
||||
background: white url(../img/pagination-bg.gif) left 180% repeat-x;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
border: 1px solid #DDD;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
table.paleblue + ul.pagination > li {
|
||||
float: left;
|
||||
line-height: 22px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
table.paleblue + ul.pagination > li:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
table.paleblue + ul.pagination > li.cardinality {
|
||||
float: right;
|
||||
color: #8d8d8d;
|
||||
}
|
||||
|
||||
table.paleblue > tbody > tr > td > span.true,
|
||||
table.paleblue > tbody > tr > td > span.false {
|
||||
background-position: top left;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
overflow: hidden;
|
||||
text-indent: -200px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
table.paleblue > tbody > tr > td > .missing {
|
||||
background: transparent url(../img/missing.png) right center no-repeat;
|
||||
color: #717171;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
table.paleblue > tbody > tr > td > .missing:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
table.paleblue > tbody > tr > td > span.true {
|
||||
background-image: url(../img/true.gif);
|
||||
}
|
||||
|
||||
table.paleblue > tbody > tr > td > span.false {
|
||||
background-image: url(../img/false.gif);
|
||||
}
|
||||
|
||||
div.table-container {
|
||||
display: inline-block;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 216 B |
Binary file not shown.
Before Width: | Height: | Size: 202 B |
Binary file not shown.
Before Width: | Height: | Size: 246 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue