Compare commits

..

8 Commits
main ... pw

1181 changed files with 89656 additions and 24058 deletions

View File

@ -1,5 +0,0 @@
[run]
dynamic_context = test_function
[html]
show_contexts = True

View File

@ -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

7
.gitignore vendored
View File

@ -1,7 +1,2 @@
local_settings.py
archive/
*.pyc
mbox/
media/
/docbow.db
/docbow-venv
*.mo

View File

@ -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 ]

53
Jenkinsfile vendored
View File

@ -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()
}
}
}

View File

@ -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

17
Makefile Normal file
View File

@ -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

View File

@ -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

View File

@ -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(';')

5
debian/changelog vendored
View File

@ -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

View File

@ -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;
}
}

44
debian/conf/magic vendored
View File

@ -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

32
debian/control vendored
View File

@ -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

View File

@ -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())

10
debian/dirs vendored
View File

@ -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

25
debian/docbow-manage vendored
View File

@ -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} "$@"

View File

@ -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

26
debian/docbow.service vendored
View File

@ -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

3
debian/docs vendored
View File

@ -1,3 +0,0 @@
README.rst
help/fr/build-pfwb
help/fr/build-pw

5
debian/install vendored
View File

@ -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
debian/links vendored
View File

@ -1 +0,0 @@
/usr/share/docbow/magic /etc/magic

14
debian/logrotate vendored
View File

@ -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
}

69
debian/postinst vendored
View File

@ -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

24
debian/postrm vendored
View File

@ -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

38
debian/preinst vendored
View File

@ -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

View File

@ -1,4 +0,0 @@
django_watson python3-django-watson
django_journal python3-django-journal
python_magic python3-magic
typing

16
debian/rules vendored
View File

@ -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

View File

@ -1 +0,0 @@
3.0 (quilt)

47
debian/uwsgi.ini vendored
View File

@ -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 =

View File

@ -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)

29
docbow_project/actions.py Normal file
View File

@ -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")

View 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

View File

@ -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'),
)

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -1,8 +0,0 @@
import django.apps
class AppConfig(django.apps.AppConfig):
name = 'docbow_project.docbow'
def ready(self):
from . import signals # noqa: F401

View File

@ -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

View File

@ -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'),
]

View File

@ -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)

View File

@ -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

View File

@ -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),
}

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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" } }
]

View File

@ -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"}}
]

View File

@ -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
}
]

View File

@ -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
)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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()

View File

@ -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()

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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']

View File

@ -1,13 +0,0 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('docbow', '0001_initial'),
]
operations = [
migrations.DeleteModel(
name='DeletedMailbox',
),
]

View File

@ -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']

View File

@ -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,
),
),
]

View File

@ -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']

View File

@ -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']

View File

@ -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),
),
]

View File

@ -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']

View File

@ -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),
),
]

View File

@ -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']

View File

@ -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'),
),
]

View File

@ -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']

View File

@ -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'),
),
]

View File

@ -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

View File

@ -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),
)

View File

@ -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

View File

@ -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)

View File

@ -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'))

View File

@ -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)

View File

@ -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
)

View File

@ -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'''

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -1,8 +0,0 @@
.table-container th.asc:after {
content: '\0000a0\0025b2';
float: right;
}
.table-container th.desc:after {
content: '\0000a0\0025bc';
float: right;
}

View File

@ -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