Merge branch 'versions/minor' into features/workflow_email_action
Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
36
HISTORY.rst
36
HISTORY.rst
@@ -15,6 +15,42 @@ Importer branch
|
|||||||
- Add support for source column exclusion.
|
- Add support for source column exclusion.
|
||||||
- Backport workflow context support.
|
- Backport workflow context support.
|
||||||
- Backport workflow transitions field support.
|
- Backport workflow transitions field support.
|
||||||
|
- Backport workflow email action.
|
||||||
|
- Backport individual index rebuild support.
|
||||||
|
- Rename the installjavascript command to installdependencies.
|
||||||
|
- Remove database conversion command.
|
||||||
|
- Remove support for quoted configuration entries. Support unquoted,
|
||||||
|
nested dictionaries in the configuration. Requires manual
|
||||||
|
update of existing config.yml files.
|
||||||
|
- Support user specified locations for the configuration file with the
|
||||||
|
CONFIGURATION_FILEPATH (MAYAN_CONFIGURATION_FILEPATH environment variable),
|
||||||
|
and CONFIGURATION_LAST_GOOD_FILEPATH
|
||||||
|
(MAYAN_CONFIGURATION_LAST_GOOD_FILEPATH environment variable) settings.
|
||||||
|
- Move bootstrapped settings code to their own module in the smart_settings
|
||||||
|
apps.
|
||||||
|
- Remove individual database configuration options. All database
|
||||||
|
configuration is now done using MAYAN_DATABASES to mirror Django way of
|
||||||
|
doing atabase etup.
|
||||||
|
- Added support for YAML encoded environment variables to the platform
|
||||||
|
templates apps.
|
||||||
|
- Move YAML code to its own module.
|
||||||
|
- Move Django and Celery settings.
|
||||||
|
- Backport FakeStorageSubclass from versions/next.
|
||||||
|
- Remove django-environ.
|
||||||
|
- Support checking in and out multiple documents.
|
||||||
|
- Remove encapsulate helper.
|
||||||
|
- Add support for menu inheritance.
|
||||||
|
- Emphasize source column labels.
|
||||||
|
|
||||||
|
3.2.6 (2019-07-10)
|
||||||
|
==================
|
||||||
|
* Remove the smart settings app * import.
|
||||||
|
* Encode settings YAML before hashing.
|
||||||
|
* Fix document icon used in the workflow runtime links.
|
||||||
|
* Add trashed date time label.
|
||||||
|
* Fix thumbnail generation issue. GitLab issue #637.
|
||||||
|
Thanks to Giacomo Cariello (@giacomocariello) for the report
|
||||||
|
and the merge request fixing the issue.
|
||||||
|
|
||||||
3.2.5 (2019-07-05)
|
3.2.5 (2019-07-05)
|
||||||
==================
|
==================
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
INSTALLATION_DIRECTORY=/home/vagrant/mayan-edms/
|
|
||||||
DB_NAME=mayan_edms
|
|
||||||
DB_PASSWORD=test123
|
|
||||||
|
|
||||||
cat << EOF | sudo tee -a /etc/motd.tail
|
|
||||||
**********************************sudo apt
|
|
||||||
|
|
||||||
Mayan EDMS Vagrant Development Box
|
|
||||||
|
|
||||||
**********************************
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Update sources
|
|
||||||
echo -e "\n -> Running apt-get update & upgrade \n"
|
|
||||||
sudo apt-get -qq update
|
|
||||||
sudo apt-get -y upgrade
|
|
||||||
|
|
||||||
echo -e "\n -> Installing core binaries \n"
|
|
||||||
sudo apt-get -y install git-core python-virtualenv gcc python-dev libjpeg-dev libpng-dev libtiff-dev tesseract-ocr poppler-utils libreoffice
|
|
||||||
|
|
||||||
echo -e "\n -> Cloning development branch of repository \n"
|
|
||||||
git clone /mayan-edms-repository/ $INSTALLATION_DIRECTORY
|
|
||||||
cd $INSTALLATION_DIRECTORY
|
|
||||||
git checkout development
|
|
||||||
git reset HEAD --hard
|
|
||||||
|
|
||||||
echo -e "\n -> Setting up virtual env \n"
|
|
||||||
virtualenv venv
|
|
||||||
source venv/bin/activate
|
|
||||||
|
|
||||||
echo -e "\n -> Installing python dependencies \n"
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
echo -e "\n -> Running Mayan EDMS initial setup \n"
|
|
||||||
./manage.py initialsetup
|
|
||||||
|
|
||||||
echo -e "\n -> Installing Redis server \n"
|
|
||||||
sudo apt-get install -y redis-server
|
|
||||||
pip install redis
|
|
||||||
|
|
||||||
echo -e "\n -> Installing testing software \n"
|
|
||||||
pip install coverage
|
|
||||||
|
|
||||||
echo -e "\n -> Installing MySQL \n"
|
|
||||||
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password password '$DB_PASSWORD
|
|
||||||
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password '$DB_PASSWORD
|
|
||||||
sudo apt-get install -y mysql-server libmysqlclient-dev
|
|
||||||
# Create a passwordless root and travis users
|
|
||||||
mysql -u root -p$DB_PASSWORD -e "SET PASSWORD = PASSWORD('');"
|
|
||||||
mysql -u root -e "CREATE USER 'travis'@'localhost' IDENTIFIED BY '';GRANT ALL PRIVILEGES ON * . * TO 'travis'@'localhost';FLUSH PRIVILEGES;"
|
|
||||||
mysql -u travis -e "CREATE DATABASE $DB_NAME;"
|
|
||||||
pip install mysql-python
|
|
||||||
|
|
||||||
echo -e "\n -> Installing PostgreSQL \n"
|
|
||||||
sudo apt-get install -y postgresql postgresql-server-dev-all
|
|
||||||
sudo -u postgres psql -c 'create database mayan_edms;' -U postgres
|
|
||||||
sudo cat > /etc/postgresql/9.3/main/pg_hba.conf << EOF
|
|
||||||
local all postgres trust
|
|
||||||
|
|
||||||
# TYPE DATABASE USER ADDRESS METHOD
|
|
||||||
|
|
||||||
# "local" is for Unix domain socket connections only
|
|
||||||
local all all peer
|
|
||||||
# IPv4 local connections:
|
|
||||||
host all all 127.0.0.1/32 md5
|
|
||||||
# IPv6 local connections:
|
|
||||||
host all all ::1/128 md5
|
|
||||||
EOF
|
|
||||||
|
|
||||||
pip install -q psycopg2
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,171 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# ====== CONFIG ======
|
|
||||||
INSTALLATION_DIRECTORY=/usr/share/mayan-edms/
|
|
||||||
DB_NAME=mayan_edms
|
|
||||||
DB_USERNAME=mayan
|
|
||||||
DB_PASSWORD=test123
|
|
||||||
# ==== END CONFIG ====
|
|
||||||
|
|
||||||
cat << EOF | tee -a /etc/motd.tail
|
|
||||||
**********************************
|
|
||||||
|
|
||||||
Mayan EDMS Vagrant Production Box
|
|
||||||
|
|
||||||
**********************************
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo -e "\n -> Running apt-get update & upgrade \n"
|
|
||||||
apt-get -qq update
|
|
||||||
apt-get -y upgrade
|
|
||||||
|
|
||||||
echo -e "\n -> Installing core binaries \n"
|
|
||||||
apt-get install nginx supervisor redis-server postgresql libpq-dev libjpeg-dev libmagic1 libpng-dev libreoffice libtiff-dev gcc ghostscript gpgv python-dev python-virtualenv tesseract-ocr poppler-utils -y
|
|
||||||
|
|
||||||
echo -e "\n -> Setting up virtualenv \n"
|
|
||||||
rm -f ${INSTALLATION_DIRECTORY}
|
|
||||||
virtualenv ${INSTALLATION_DIRECTORY}
|
|
||||||
source ${INSTALLATION_DIRECTORY}bin/activate
|
|
||||||
|
|
||||||
echo -e "\n -> Installing Mayan EDMS from PyPI \n"
|
|
||||||
pip install mayan-edms
|
|
||||||
|
|
||||||
echo -e "\n -> Installing Python client for PostgreSQL, Redis, and uWSGI \n"
|
|
||||||
pip install psycopg2 redis uwsgi
|
|
||||||
|
|
||||||
echo -e "\n -> Creating the database for the installation \n"
|
|
||||||
echo "CREATE USER mayan WITH PASSWORD '$DB_PASSWORD';" | sudo -u postgres psql
|
|
||||||
sudo -u postgres createdb -O $DB_USERNAME $DB_NAME
|
|
||||||
|
|
||||||
echo -e "\n -> Creating the directories for the logs \n"
|
|
||||||
mkdir /var/log/mayan
|
|
||||||
|
|
||||||
echo -e "\n -> Making a convenience symlink \n"
|
|
||||||
cd ${INSTALLATION_DIRECTORY}
|
|
||||||
ln -s lib/python2.7/site-packages/mayan .
|
|
||||||
|
|
||||||
echo -e "\n -> Creating an initial settings file \n"
|
|
||||||
mayan-edms.py createsettings
|
|
||||||
|
|
||||||
echo -e "\n -> Updating the mayan/settings/local.py file \n"
|
|
||||||
cat >> mayan/settings/local.py << EOF
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
|
||||||
'NAME': '$DB_NAME',
|
|
||||||
'USER': '$DB_USERNAME',
|
|
||||||
'PASSWORD': '$DB_PASSWORD',
|
|
||||||
'HOST': 'localhost',
|
|
||||||
'PORT': '5432',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BROKER_URL = 'redis://127.0.0.1:6379/0'
|
|
||||||
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo -e "\n -> Migrating the database or initialize the project \n"
|
|
||||||
mayan-edms.py initialsetup
|
|
||||||
|
|
||||||
echo -e "\n -> Disabling the default NGINX site \n"
|
|
||||||
rm -f /etc/nginx/sites-enabled/default
|
|
||||||
|
|
||||||
echo -e "\n -> Creating a uwsgi.ini file \n"
|
|
||||||
cat > uwsgi.ini << EOF
|
|
||||||
[uwsgi]
|
|
||||||
chdir = ${INSTALLATION_DIRECTORY}lib/python2.7/site-packages/mayan
|
|
||||||
chmod-socket = 664
|
|
||||||
chown-socket = www-data:www-data
|
|
||||||
env = DJANGO_SETTINGS_MODULE=mayan.settings.production
|
|
||||||
gid = www-data
|
|
||||||
logto = /var/log/uwsgi/%n.log
|
|
||||||
pythonpath = ${INSTALLATION_DIRECTORY}lib/python2.7/site-packages
|
|
||||||
master = True
|
|
||||||
max-requests = 5000
|
|
||||||
socket = ${INSTALLATION_DIRECTORY}uwsgi.sock
|
|
||||||
uid = www-data
|
|
||||||
vacuum = True
|
|
||||||
wsgi-file = ${INSTALLATION_DIRECTORY}lib/python2.7/site-packages/mayan/wsgi.py
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo -e "\n -> Creating the directory for the uWSGI log files \n"
|
|
||||||
mkdir -p /var/log/uwsgi
|
|
||||||
|
|
||||||
echo -e "\n -> Creating the NGINX site file for Mayan EDMS, /etc/nginx/sites-available/mayan \n"
|
|
||||||
cat > /etc/nginx/sites-available/mayan << EOF
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
include uwsgi_params;
|
|
||||||
uwsgi_pass unix:${INSTALLATION_DIRECTORY}uwsgi.sock;
|
|
||||||
|
|
||||||
client_max_body_size 30M; # Increse if your plan to upload bigger documents
|
|
||||||
proxy_read_timeout 30s; # Increase if your document uploads take more than 30 seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
location /static {
|
|
||||||
alias ${INSTALLATION_DIRECTORY}mayan/media/static;
|
|
||||||
expires 1h;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /favicon.ico {
|
|
||||||
alias ${INSTALLATION_DIRECTORY}mayan/media/static/appearance/images/favicon.ico;
|
|
||||||
expires 1h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo -e "\n -> Enabling the NGINX site for Mayan EDMS \n"
|
|
||||||
ln -s /etc/nginx/sites-available/mayan /etc/nginx/sites-enabled/
|
|
||||||
|
|
||||||
echo -e "\n -> Creating the supervisor file for the uWSGI process, /etc/supervisor/conf.d/mayan-uwsgi.conf \n"
|
|
||||||
cat > /etc/supervisor/conf.d/mayan-uwsgi.conf << EOF
|
|
||||||
[program:mayan-uwsgi]
|
|
||||||
command = ${INSTALLATION_DIRECTORY}bin/uwsgi --ini ${INSTALLATION_DIRECTORY}uwsgi.ini
|
|
||||||
user = root
|
|
||||||
autostart = true
|
|
||||||
autorestart = true
|
|
||||||
redirect_stderr = true
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo -e "\n -> Creating the supervisor file for the Celery worker, /etc/supervisor/conf.d/mayan-celery.conf \n"
|
|
||||||
cat > /etc/supervisor/conf.d/mayan-celery.conf << EOF
|
|
||||||
[program:mayan-worker]
|
|
||||||
command = ${INSTALLATION_DIRECTORY}bin/python ${INSTALLATION_DIRECTORY}bin/mayan-edms.py celery --settings=mayan.settings.production worker -Ofair -l ERROR
|
|
||||||
directory = ${INSTALLATION_DIRECTORY}
|
|
||||||
user = www-data
|
|
||||||
stdout_logfile = /var/log/mayan/worker-stdout.log
|
|
||||||
stderr_logfile = /var/log/mayan/worker-stderr.log
|
|
||||||
autostart = true
|
|
||||||
autorestart = true
|
|
||||||
startsecs = 10
|
|
||||||
stopwaitsecs = 10
|
|
||||||
killasgroup = true
|
|
||||||
priority = 998
|
|
||||||
|
|
||||||
[program:mayan-beat]
|
|
||||||
command = ${INSTALLATION_DIRECTORY}bin/python ${INSTALLATION_DIRECTORY}bin/mayan-edms.py celery --settings=mayan.settings.production beat -l ERROR
|
|
||||||
directory = ${INSTALLATION_DIRECTORY}
|
|
||||||
user = www-data
|
|
||||||
numprocs = 1
|
|
||||||
stdout_logfile = /var/log/mayan/beat-stdout.log
|
|
||||||
stderr_logfile = /var/log/mayan/beat-stderr.log
|
|
||||||
autostart = true
|
|
||||||
autorestart = true
|
|
||||||
startsecs = 10
|
|
||||||
stopwaitsecs = 1
|
|
||||||
killasgroup = true
|
|
||||||
priority = 998
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo -e "\n -> Collecting the static files \n"
|
|
||||||
mayan-edms.py preparestatic --noinput
|
|
||||||
|
|
||||||
echo -e "\n -> Making the installation directory readable and writable by the webserver user \n"
|
|
||||||
chown www-data:www-data ${INSTALLATION_DIRECTORY} -R
|
|
||||||
|
|
||||||
echo -e "\n -> Restarting the services \n"
|
|
||||||
/etc/init.d/nginx restart
|
|
||||||
/etc/init.d/supervisor restart
|
|
||||||
@@ -14,7 +14,7 @@ APP_LIST = (
|
|||||||
'django_gpg', 'document_comments', 'document_indexing',
|
'django_gpg', 'document_comments', 'document_indexing',
|
||||||
'document_parsing', 'document_signatures', 'document_states',
|
'document_parsing', 'document_signatures', 'document_states',
|
||||||
'documents', 'dynamic_search', 'events', 'file_metadata', 'linking',
|
'documents', 'dynamic_search', 'events', 'file_metadata', 'linking',
|
||||||
'lock_manager', 'mayan_statistics', 'mailer', 'metadata', 'mirroring',
|
'lock_manager', 'mailer', 'mayan_statistics', 'metadata', 'mirroring',
|
||||||
'motd', 'navigation', 'ocr', 'permissions', 'platform', 'rest_api',
|
'motd', 'navigation', 'ocr', 'permissions', 'platform', 'rest_api',
|
||||||
'smart_settings', 'sources', 'storage', 'tags', 'task_manager',
|
'smart_settings', 'sources', 'storage', 'tags', 'task_manager',
|
||||||
'user_management'
|
'user_management'
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
NAME="mayan-edms"
|
|
||||||
DJANGODIR=/usr/share/mayan-edms
|
|
||||||
SOCKFILE=/var/tmp/filesystem.sock
|
|
||||||
USER=www-data
|
|
||||||
GROUP=www-data
|
|
||||||
NUM_WORKERS=3
|
|
||||||
DJANGO_SETTINGS_MODULE=mayan.settings.production
|
|
||||||
DJANGO_WSGI_MODULE=mayan.wsgi
|
|
||||||
TIMEOUT=600
|
|
||||||
|
|
||||||
echo "Starting $NAME as `whoami`"
|
|
||||||
|
|
||||||
# Activate the virtual environment
|
|
||||||
cd $DJANGODIR
|
|
||||||
source bin/activate
|
|
||||||
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
|
|
||||||
export PYTHONPATH=$DJANGODIR:$PYTHONPATH
|
|
||||||
|
|
||||||
# Create the run directory if it doesn't exist
|
|
||||||
RUNDIR=$(dirname $SOCKFILE)
|
|
||||||
test -d $RUNDIR || mkdir -p $RUNDIR
|
|
||||||
|
|
||||||
# Start your Django Unicorn
|
|
||||||
# Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon)
|
|
||||||
exec bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
|
|
||||||
--name $NAME \
|
|
||||||
--workers $NUM_WORKERS \
|
|
||||||
--user=$USER --group=$GROUP \
|
|
||||||
--log-level=debug \
|
|
||||||
--bind=unix:$SOCKFILE \
|
|
||||||
--timeout=$TIMEOUT
|
|
||||||
|
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ RUN python -m virtualenv "${PROJECT_INSTALL_DIR}" \
|
|||||||
# Install the built Mayan EDMS package
|
# Install the built Mayan EDMS package
|
||||||
&& pip install --no-cache-dir --no-use-pep517 dist/mayan* \
|
&& pip install --no-cache-dir --no-use-pep517 dist/mayan* \
|
||||||
# Install the static content
|
# Install the static content
|
||||||
&& mayan-edms.py installjavascript \
|
&& mayan-edms.py installdependencies \
|
||||||
&& MAYAN_STATIC_ROOT=${PROJECT_INSTALL_DIR}/static mayan-edms.py preparestatic --link --noinput
|
&& MAYAN_STATIC_ROOT=${PROJECT_INSTALL_DIR}/static mayan-edms.py preparestatic --link --noinput
|
||||||
|
|
||||||
COPY --chown=mayan:mayan requirements/testing-base.txt "${PROJECT_INSTALL_DIR}"
|
COPY --chown=mayan:mayan requirements/testing-base.txt "${PROJECT_INSTALL_DIR}"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3.2.5
|
3.2.6
|
||||||
|
|||||||
@@ -127,9 +127,8 @@ For another setup that offers more performance and scalability refer to the
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
sudo -u mayan MAYAN_DATABASE_ENGINE=django.db.backends.postgresql MAYAN_DATABASE_NAME=mayan \
|
sudo -u mayan MAYAN_DATABASES="{'default':{'ENGINE':'django.db.backends.postgresql','NAME':'mayan','PASSWORD':'mayanuserpass','USER':'mayan','HOST':'127.0.0.1'}}" \
|
||||||
MAYAN_DATABASE_PASSWORD=mayanuserpass MAYAN_DATABASE_USER=mayan \
|
MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
||||||
MAYAN_DATABASE_HOST=127.0.0.1 MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
|
||||||
/opt/mayan-edms/bin/mayan-edms.py initialsetup
|
/opt/mayan-edms/bin/mayan-edms.py initialsetup
|
||||||
|
|
||||||
|
|
||||||
@@ -148,9 +147,8 @@ For another setup that offers more performance and scalability refer to the
|
|||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
::
|
::
|
||||||
|
|
||||||
sudo MAYAN_DATABASE_ENGINE=django.db.backends.postgresql MAYAN_DATABASE_NAME=mayan \
|
sudo mayan MAYAN_DATABASES="{'default':{'ENGINE':'django.db.backends.postgresql','NAME':'mayan','PASSWORD':'mayanuserpass','USER':'mayan','HOST':'127.0.0.1'}}" \
|
||||||
MAYAN_DATABASE_PASSWORD=mayanuserpass MAYAN_DATABASE_USER=mayan \
|
MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
||||||
MAYAN_DATABASE_HOST=127.0.0.1 MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
|
||||||
/opt/mayan-edms/bin/mayan-edms.py platformtemplate supervisord > /etc/supervisor/conf.d/mayan.conf
|
/opt/mayan-edms/bin/mayan-edms.py platformtemplate supervisord > /etc/supervisor/conf.d/mayan.conf
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
111
docs/releases/3.2.6.rst
Normal file
111
docs/releases/3.2.6.rst
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
Version 3.2.6
|
||||||
|
=============
|
||||||
|
|
||||||
|
Released: July 10, 2019
|
||||||
|
|
||||||
|
|
||||||
|
Changes
|
||||||
|
-------
|
||||||
|
|
||||||
|
- Remove the smart settings app * import. Following MERC 0005.
|
||||||
|
- Encode settings YAML before hashing. Avoids unicode issues with Python 3.
|
||||||
|
- Fix document icon used in the workflow runtime links.
|
||||||
|
- Add trashed date time label.
|
||||||
|
- Fix thumbnail generation issue. GitLab issue #637.
|
||||||
|
Thanks to Giacomo Cariello (@giacomocariello) for the report
|
||||||
|
and the merge request fixing the issue.
|
||||||
|
|
||||||
|
Removals
|
||||||
|
--------
|
||||||
|
|
||||||
|
- None
|
||||||
|
|
||||||
|
|
||||||
|
Upgrading from a previous version
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
If installed via Python's PIP
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Remove deprecated requirements::
|
||||||
|
|
||||||
|
sudo -u mayan curl https://gitlab.com/mayan-edms/mayan-edms/raw/master/removals.txt -o /tmp/removals.txt && sudo -u mayan /opt/mayan-edms/bin/pip uninstall -y -r /tmp/removals.txt
|
||||||
|
|
||||||
|
Type in the console::
|
||||||
|
|
||||||
|
sudo -u mayan /opt/mayan-edms/bin/pip install mayan-edms==3.2.6
|
||||||
|
|
||||||
|
the requirements will also be updated automatically.
|
||||||
|
|
||||||
|
|
||||||
|
Using Git
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
If you installed Mayan EDMS by cloning the Git repository issue the commands::
|
||||||
|
|
||||||
|
$ git reset --hard HEAD
|
||||||
|
$ git pull
|
||||||
|
|
||||||
|
otherwise download the compressed archived and uncompress it overriding the
|
||||||
|
existing installation.
|
||||||
|
|
||||||
|
Remove deprecated requirements::
|
||||||
|
|
||||||
|
$ pip uninstall -y -r removals.txt
|
||||||
|
|
||||||
|
Next upgrade/add the new requirements::
|
||||||
|
|
||||||
|
$ pip install --upgrade -r requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
Common steps
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Perform these steps after updating the code from either step above.
|
||||||
|
|
||||||
|
Make a backup of your supervisord file::
|
||||||
|
|
||||||
|
sudo cp /etc/supervisor/conf.d/mayan.conf /etc/supervisor/conf.d/mayan.conf.bck
|
||||||
|
|
||||||
|
Update the supervisord configuration file. Replace the environment
|
||||||
|
variables values show here with your respective settings. This step will refresh
|
||||||
|
the supervisord configuration file with the new queues and the latest
|
||||||
|
recommended layout::
|
||||||
|
|
||||||
|
sudo MAYAN_DATABASE_ENGINE=django.db.backends.postgresql MAYAN_DATABASE_NAME=mayan \
|
||||||
|
MAYAN_DATABASE_PASSWORD=mayanuserpass MAYAN_DATABASE_USER=mayan \
|
||||||
|
MAYAN_DATABASE_HOST=127.0.0.1 MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
||||||
|
/opt/mayan-edms/bin/mayan-edms.py platformtemplate supervisord > /etc/supervisor/conf.d/mayan.conf
|
||||||
|
|
||||||
|
Edit the supervisord configuration file and update any setting the template
|
||||||
|
generator missed::
|
||||||
|
|
||||||
|
sudo vi /etc/supervisor/conf.d/mayan.conf
|
||||||
|
|
||||||
|
Migrate existing database schema with::
|
||||||
|
|
||||||
|
sudo -u mayan MAYAN_DATABASE_ENGINE=django.db.backends.postgresql MAYAN_DATABASE_NAME=mayan \
|
||||||
|
MAYAN_DATABASE_PASSWORD=mayanuserpass MAYAN_DATABASE_USER=mayan \
|
||||||
|
MAYAN_DATABASE_HOST=127.0.0.1 MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
||||||
|
/opt/mayan-edms/bin/mayan-edms.py performupgrade
|
||||||
|
|
||||||
|
Add new static media::
|
||||||
|
|
||||||
|
sudo -u mayan MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
||||||
|
/opt/mayan-edms/bin/mayan-edms.py preparestatic --noinput
|
||||||
|
|
||||||
|
The upgrade procedure is now complete.
|
||||||
|
|
||||||
|
|
||||||
|
Backward incompatible changes
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
- None
|
||||||
|
|
||||||
|
|
||||||
|
Bugs fixed or issues closed
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
- :gitlab-issue:`637` Thumbnail generation bug
|
||||||
|
|
||||||
|
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/
|
||||||
@@ -22,11 +22,55 @@ Changes
|
|||||||
- Add support for source column exclusion.
|
- Add support for source column exclusion.
|
||||||
- Backport workflow context support.
|
- Backport workflow context support.
|
||||||
- Backport workflow transitions field support.
|
- Backport workflow transitions field support.
|
||||||
|
- Backport workflow email action.
|
||||||
|
- Backport individual index rebuild support.
|
||||||
|
- Rename the installjavascript command to installdependencies.
|
||||||
|
- Remove database conversion command.
|
||||||
|
- Remove support for quoted configuration entries. Support unquoted,
|
||||||
|
nested dictionaries in the configuration. Requires manual
|
||||||
|
update of existing config.yml files.
|
||||||
|
- Support user specified locations for the configuration file with the
|
||||||
|
CONFIGURATION_FILEPATH (MAYAN_CONFIGURATION_FILEPATH environment variable), and
|
||||||
|
CONFIGURATION_LAST_GOOD_FILEPATH
|
||||||
|
(MAYAN_CONFIGURATION_LAST_GOOD_FILEPATH environment variable) settings.
|
||||||
|
- Move bootstrapped settings code to their own module in the smart_settings apps.
|
||||||
|
- Remove individual database configuration options. All database configuration
|
||||||
|
is now done using MAYAN_DATABASES to mirror Django way of doing database setup.
|
||||||
|
- Added support for YAML encoded environment variables to the platform
|
||||||
|
templates apps.
|
||||||
|
- Move YAML code to its own module. Code now resides in common.serialization
|
||||||
|
in the form of two new functions: yaml_load and yaml_dump.
|
||||||
|
- Move Django and Celery settings. Django settings now reside in the smart
|
||||||
|
settings app. Celery settings now reside in the task manager app.
|
||||||
|
- Backport FakeStorageSubclass from versions/next. Placeholder class to allow
|
||||||
|
serializing the real storage subclass to support migrations.
|
||||||
|
Used by all configurable storages.
|
||||||
|
- Support checking in and out multiple documents.
|
||||||
|
- Remove encapsulate helper.
|
||||||
|
- Add support for menu inheritance.
|
||||||
|
- Emphasize source column labels.
|
||||||
|
|
||||||
Removals
|
Removals
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- None
|
- Database conversion. Reason for removal. The database conversions support
|
||||||
|
provided by this feature (SQLite to PostgreSQL) was being confused with
|
||||||
|
database migrations and upgrades.
|
||||||
|
|
||||||
|
Database upgrades are the responsibility of the app and the framework.
|
||||||
|
Database conversions however are not the responsibility of the app (Mayan),
|
||||||
|
they are the responsibility of the framework.
|
||||||
|
|
||||||
|
Database conversion is outside the scope of what Mayan does but we added
|
||||||
|
the code, management command, instructions and testing setup to provide
|
||||||
|
this to our users until the framework (Django) decided to add this
|
||||||
|
themselves (like they did with migrations).
|
||||||
|
|
||||||
|
Continued confusion about the purpose of the feature and confusion about
|
||||||
|
how errors with this feature were a reflexion of the code quality of
|
||||||
|
Mayannecessitated the removal of the database conversion feature.
|
||||||
|
|
||||||
|
- Django environ
|
||||||
|
|
||||||
|
|
||||||
Upgrading from a previous version
|
Upgrading from a previous version
|
||||||
@@ -37,11 +81,11 @@ If installed via Python's PIP
|
|||||||
|
|
||||||
Remove deprecated requirements::
|
Remove deprecated requirements::
|
||||||
|
|
||||||
$ curl https://gitlab.com/mayan-edms/mayan-edms/raw/master/removals.txt | pip uninstall -r /dev/stdin
|
sudo -u mayan curl https://gitlab.com/mayan-edms/mayan-edms/raw/master/removals.txt -o /tmp/removals.txt && sudo -u mayan /opt/mayan-edms/bin/pip uninstall -y -r /tmp/removals.txt
|
||||||
|
|
||||||
Type in the console::
|
Type in the console::
|
||||||
|
|
||||||
$ pip install mayan-edms==3.3
|
/opt/mayan-edms/bin/pip install mayan-edms==3.3
|
||||||
|
|
||||||
the requirements will also be updated automatically.
|
the requirements will also be updated automatically.
|
||||||
|
|
||||||
@@ -51,19 +95,19 @@ Using Git
|
|||||||
|
|
||||||
If you installed Mayan EDMS by cloning the Git repository issue the commands::
|
If you installed Mayan EDMS by cloning the Git repository issue the commands::
|
||||||
|
|
||||||
$ git reset --hard HEAD
|
git reset --hard HEAD
|
||||||
$ git pull
|
git pull
|
||||||
|
|
||||||
otherwise download the compressed archived and uncompress it overriding the
|
otherwise download the compressed archived and uncompress it overriding the
|
||||||
existing installation.
|
existing installation.
|
||||||
|
|
||||||
Remove deprecated requirements::
|
Remove deprecated requirements::
|
||||||
|
|
||||||
$ pip uninstall -y -r removals.txt
|
pip uninstall -y -r removals.txt
|
||||||
|
|
||||||
Next upgrade/add the new requirements::
|
Next upgrade/add the new requirements::
|
||||||
|
|
||||||
$ pip install --upgrade -r requirements.txt
|
pip install --upgrade -r requirements.txt
|
||||||
|
|
||||||
|
|
||||||
Common steps
|
Common steps
|
||||||
@@ -80,9 +124,8 @@ variables values show here with your respective settings. This step will refresh
|
|||||||
the supervisord configuration file with the new queues and the latest
|
the supervisord configuration file with the new queues and the latest
|
||||||
recommended layout::
|
recommended layout::
|
||||||
|
|
||||||
sudo MAYAN_DATABASE_ENGINE=django.db.backends.postgresql MAYAN_DATABASE_NAME=mayan \
|
sudo MAYAN_DATABASES="{'default':{'ENGINE':'django.db.backends.postgresql','NAME':'mayan','PASSWORD':'mayanuserpass','USER':'mayan','HOST':'127.0.0.1'}}" \
|
||||||
MAYAN_DATABASE_PASSWORD=mayanuserpass MAYAN_DATABASE_USER=mayan \
|
MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
||||||
MAYAN_DATABASE_HOST=127.0.0.1 MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
|
||||||
/opt/mayan-edms/bin/mayan-edms.py platformtemplate supervisord > /etc/supervisor/conf.d/mayan.conf
|
/opt/mayan-edms/bin/mayan-edms.py platformtemplate supervisord > /etc/supervisor/conf.d/mayan.conf
|
||||||
|
|
||||||
Edit the supervisord configuration file and update any setting the template
|
Edit the supervisord configuration file and update any setting the template
|
||||||
@@ -92,11 +135,11 @@ generator missed::
|
|||||||
|
|
||||||
Migrate existing database schema with::
|
Migrate existing database schema with::
|
||||||
|
|
||||||
$ mayan-edms.py performupgrade
|
sudo -u mayan MAYAN_MEDIA_ROOT=/opt/mayan-edms/media /opt/mayan-edms/bin/mayan-edms.py performupgrade
|
||||||
|
|
||||||
Add new static media::
|
Add new static media::
|
||||||
|
|
||||||
$ mayan-edms.py preparestatic --noinput
|
sudo -u mayan MAYAN_MEDIA_ROOT=/opt/mayan-edms/media /opt/mayan-edms/bin/mayan-edms.py preparestatic --noinput
|
||||||
|
|
||||||
The upgrade procedure is now complete.
|
The upgrade procedure is now complete.
|
||||||
|
|
||||||
@@ -104,12 +147,20 @@ The upgrade procedure is now complete.
|
|||||||
Backward incompatible changes
|
Backward incompatible changes
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
- None
|
- Update quoted settings to be unquoted:
|
||||||
|
|
||||||
|
- COMMON_SHARED_STORAGE_ARGUMENTS
|
||||||
|
- CONVERTER_GRAPHICS_BACKEND_ARGUMENTS
|
||||||
|
- DOCUMENTS_CACHE_STORAGE_BACKEND_ARGUMENTS
|
||||||
|
- DOCUMENTS_STORAGE_BACKEND_ARGUMENTS
|
||||||
|
- FILE_METADATA_DRIVERS_ARGUMENTS
|
||||||
|
- SIGNATURES_STORAGE_BACKEND_ARGUMENTS
|
||||||
|
|
||||||
|
|
||||||
Bugs fixed or issues closed
|
Bugs fixed or issues closed
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
- :gitlab-issue:`532` Workflow preview isn't updated right after transitions are modified
|
- :gitlab-issue:`532` Workflow preview isn't updated right after transitions are modified
|
||||||
|
- :gitlab-issue:`634` Failing docker entrypoint when using secret config
|
||||||
|
|
||||||
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/
|
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ versions of the documentation contain the release notes for any later releases.
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
3.3
|
3.3
|
||||||
|
3.2.6
|
||||||
3.2.5
|
3.2.5
|
||||||
3.2.4
|
3.2.4
|
||||||
3.2.3
|
3.2.3
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
__title__ = 'Mayan EDMS'
|
__title__ = 'Mayan EDMS'
|
||||||
__version__ = '3.2.5'
|
__version__ = '3.2.6'
|
||||||
__build__ = 0x030205
|
__build__ = 0x030206
|
||||||
__build_string__ = 'v3.2.5_Fri Jul 5 16:39:17 2019 -0400'
|
__build_string__ = 'v3.2.6_Wed Jul 10 03:18:15 2019 -0400'
|
||||||
__django_version__ = '1.11'
|
__django_version__ = '1.11'
|
||||||
__author__ = 'Roberto Rosario'
|
__author__ = 'Roberto Rosario'
|
||||||
__author_email__ = 'roberto.rosario@mayan-edms.com'
|
__author_email__ = 'roberto.rosario@mayan-edms.com'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
from .literals import DEFAULT_MAXIMUM_TITLE_LENGTH
|
from .literals import DEFAULT_MAXIMUM_TITLE_LENGTH
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,10 @@ a i {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.source-column-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
/* Content */
|
/* Content */
|
||||||
@media (min-width:1200px) {
|
@media (min-width:1200px) {
|
||||||
.container-fluid {
|
.container-fluid {
|
||||||
@@ -264,8 +268,8 @@ a i {
|
|||||||
|
|
||||||
#ajax-spinner {
|
#ajax-spinner {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 12px;
|
top: 16px;
|
||||||
right: 10px;
|
left: 10px;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
{% if not hide_columns %}
|
{% if not hide_columns %}
|
||||||
{% navigation_get_source_columns source=object exclude_identifier=True as source_columns %}
|
{% navigation_get_source_columns source=object exclude_identifier=True as source_columns %}
|
||||||
{% for column in source_columns %}
|
{% for column in source_columns %}
|
||||||
<div class="text-center" style="">{% navigation_source_column_resolve column=column as column_value %}{% if column_value != '' %}{% if column.include_label %}{{ column.label }}: {% endif %}{{ column_value }}{% endif %}</div>
|
<div class="text-center" style="">{% navigation_source_column_resolve column=column as column_value %}{% if column_value != '' %}{% if column.include_label %}<span class="source-column-label">{{ column.label }}</span>: {% endif %}{{ column_value }}{% endif %}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
from .literals import DEFAULT_LOGIN_METHOD, DEFAULT_MAXIMUM_SESSION_LENGTH
|
from .literals import DEFAULT_LOGIN_METHOD, DEFAULT_MAXIMUM_SESSION_LENGTH
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
from .literals import DEFAULT_EMAIL, DEFAULT_PASSWORD, DEFAULT_USERNAME
|
from .literals import DEFAULT_EMAIL, DEFAULT_PASSWORD, DEFAULT_USERNAME
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from mayan.apps.acls.classes import ModelPermission
|
from mayan.apps.acls.classes import ModelPermission
|
||||||
from mayan.apps.common.apps import MayanAppConfig
|
from mayan.apps.common.apps import MayanAppConfig
|
||||||
from mayan.apps.common.menus import menu_facet, menu_main, menu_secondary
|
from mayan.apps.common.menus import (
|
||||||
|
menu_facet, menu_main, menu_multi_item, menu_secondary
|
||||||
|
)
|
||||||
from mayan.apps.dashboards.dashboards import dashboard_main
|
from mayan.apps.dashboards.dashboards import dashboard_main
|
||||||
from mayan.apps.events.classes import ModelEventType
|
from mayan.apps.events.classes import ModelEventType
|
||||||
|
from mayan.apps.navigation.classes import SourceColumn
|
||||||
|
|
||||||
from .dashboard_widgets import DashboardWidgetTotalCheckouts
|
from .dashboard_widgets import DashboardWidgetTotalCheckouts
|
||||||
from .events import (
|
from .events import (
|
||||||
@@ -17,8 +20,9 @@ from .events import (
|
|||||||
)
|
)
|
||||||
from .handlers import handler_check_new_version_creation
|
from .handlers import handler_check_new_version_creation
|
||||||
from .links import (
|
from .links import (
|
||||||
link_check_in_document, link_check_out_document, link_check_out_info,
|
link_check_in_document, link_check_in_document_multiple,
|
||||||
link_check_out_list
|
link_check_out_document, link_check_out_document_multiple,
|
||||||
|
link_check_out_info, link_check_out_list
|
||||||
)
|
)
|
||||||
from .methods import (
|
from .methods import (
|
||||||
method_check_in, method_get_check_out_info, method_get_check_out_state,
|
method_check_in, method_get_check_out_info, method_get_check_out_state,
|
||||||
@@ -43,6 +47,8 @@ class CheckoutsApp(MayanAppConfig):
|
|||||||
def ready(self):
|
def ready(self):
|
||||||
super(CheckoutsApp, self).ready()
|
super(CheckoutsApp, self).ready()
|
||||||
|
|
||||||
|
CheckedOutDocument = self.get_model(model_name='CheckedOutDocument')
|
||||||
|
DocumentCheckout = self.get_model(model_name='DocumentCheckout')
|
||||||
Document = apps.get_model(
|
Document = apps.get_model(
|
||||||
app_label='documents', model_name='Document'
|
app_label='documents', model_name='Document'
|
||||||
)
|
)
|
||||||
@@ -76,6 +82,22 @@ class CheckoutsApp(MayanAppConfig):
|
|||||||
permission_document_check_out_detail_view
|
permission_document_check_out_detail_view
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
ModelPermission.register_inheritance(
|
||||||
|
model=DocumentCheckout, related='document'
|
||||||
|
)
|
||||||
|
|
||||||
|
SourceColumn(
|
||||||
|
attribute='get_user_display', include_label=True, order=99,
|
||||||
|
source=CheckedOutDocument
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
attribute='get_checkout_datetime', include_label=True, order=99,
|
||||||
|
source=CheckedOutDocument
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
attribute='get_checkout_expiration', include_label=True, order=99,
|
||||||
|
source=CheckedOutDocument
|
||||||
|
)
|
||||||
|
|
||||||
dashboard_main.add_widget(
|
dashboard_main.add_widget(
|
||||||
widget=DashboardWidgetTotalCheckouts, order=-1
|
widget=DashboardWidgetTotalCheckouts, order=-1
|
||||||
@@ -85,6 +107,22 @@ class CheckoutsApp(MayanAppConfig):
|
|||||||
links=(link_check_out_info,), sources=(Document,)
|
links=(link_check_out_info,), sources=(Document,)
|
||||||
)
|
)
|
||||||
menu_main.bind_links(links=(link_check_out_list,), position=98)
|
menu_main.bind_links(links=(link_check_out_list,), position=98)
|
||||||
|
menu_multi_item.bind_links(
|
||||||
|
links=(
|
||||||
|
link_check_in_document_multiple,
|
||||||
|
), sources=(CheckedOutDocument,)
|
||||||
|
)
|
||||||
|
menu_multi_item.bind_links(
|
||||||
|
links=(
|
||||||
|
link_check_in_document_multiple,
|
||||||
|
link_check_out_document_multiple,
|
||||||
|
), sources=(Document,)
|
||||||
|
)
|
||||||
|
menu_multi_item.unbind_links(
|
||||||
|
links=(
|
||||||
|
link_check_out_document_multiple,
|
||||||
|
), sources=(CheckedOutDocument,)
|
||||||
|
)
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(link_check_out_document, link_check_in_document),
|
links=(link_check_out_document, link_check_in_document),
|
||||||
sources=(
|
sources=(
|
||||||
|
|||||||
@@ -38,16 +38,26 @@ link_check_out_document = Link(
|
|||||||
args='object.pk', condition=is_not_checked_out,
|
args='object.pk', condition=is_not_checked_out,
|
||||||
icon_class=icon_check_out_document,
|
icon_class=icon_check_out_document,
|
||||||
permissions=(permission_document_check_out,),
|
permissions=(permission_document_check_out,),
|
||||||
text=_('Check out document'), view='checkouts:check_out_document',
|
text=_('Check out document'), view='checkouts:check_out_document'
|
||||||
|
)
|
||||||
|
link_check_out_document_multiple = Link(
|
||||||
|
icon_class=icon_check_out_document,
|
||||||
|
permissions=(permission_document_check_out,), text=_('Check out'),
|
||||||
|
view='checkouts:check_out_document_multiple'
|
||||||
)
|
)
|
||||||
link_check_in_document = Link(
|
link_check_in_document = Link(
|
||||||
args='object.pk', icon_class=icon_check_in_document,
|
args='object.pk', icon_class=icon_check_in_document,
|
||||||
condition=is_checked_out, permissions=(
|
condition=is_checked_out, permissions=(
|
||||||
permission_document_check_in, permission_document_check_in_override
|
permission_document_check_in, permission_document_check_in_override
|
||||||
), text=_('Check in document'), view='checkouts:check_in_document',
|
), text=_('Check in document'), view='checkouts:check_in_document'
|
||||||
|
)
|
||||||
|
link_check_in_document_multiple = Link(
|
||||||
|
icon_class=icon_check_in_document,
|
||||||
|
permissions=(permission_document_check_in,), text=_('Check in'),
|
||||||
|
view='checkouts:check_in_document_multiple'
|
||||||
)
|
)
|
||||||
link_check_out_info = Link(
|
link_check_out_info = Link(
|
||||||
args='resolved_object.pk', icon_class=icon_check_out_info, permissions=(
|
args='resolved_object.pk', icon_class=icon_check_out_info, permissions=(
|
||||||
permission_document_check_out_detail_view,
|
permission_document_check_out_detail_view,
|
||||||
), text=_('Check in/out'), view='checkouts:check_out_info',
|
), text=_('Check in/out'), view='checkouts:check_out_info'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from django.apps import apps
|
|||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
from mayan.apps.acls.models import AccessControlList
|
||||||
from mayan.apps.documents.models import Document
|
from mayan.apps.documents.models import Document
|
||||||
|
|
||||||
from .events import (
|
from .events import (
|
||||||
@@ -14,10 +15,53 @@ from .events import (
|
|||||||
)
|
)
|
||||||
from .exceptions import DocumentNotCheckedOut
|
from .exceptions import DocumentNotCheckedOut
|
||||||
from .literals import STATE_CHECKED_OUT, STATE_CHECKED_IN
|
from .literals import STATE_CHECKED_OUT, STATE_CHECKED_IN
|
||||||
|
from .permissions import (
|
||||||
|
permission_document_check_in, permission_document_check_in_override
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentCheckoutBusinessLogicManager(models.Manager):
|
||||||
|
def check_in_document(self, document, user=None):
|
||||||
|
queryset = document._meta.default_manager.filter(pk=document.pk)
|
||||||
|
return self.check_in_documents(queryset=queryset, user=user)
|
||||||
|
|
||||||
|
def check_in_documents(self, queryset, user=None):
|
||||||
|
if user:
|
||||||
|
user_document_checkouts = AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission_document_check_in,
|
||||||
|
queryset=self.filter(user_id=user.pk, document__in=queryset),
|
||||||
|
user=user
|
||||||
|
)
|
||||||
|
|
||||||
|
others_document_checkouts = AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission_document_check_in_override,
|
||||||
|
queryset=self.exclude(user_id=user.pk, document__in=queryset),
|
||||||
|
user=user
|
||||||
|
)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
if user:
|
||||||
|
for checkout in user_document_checkouts:
|
||||||
|
event_document_check_in.commit(
|
||||||
|
actor=user, target=checkout.document
|
||||||
|
)
|
||||||
|
checkout.delete()
|
||||||
|
|
||||||
|
for checkout in others_document_checkouts:
|
||||||
|
event_document_forceful_check_in.commit(
|
||||||
|
actor=user, target=checkout.document
|
||||||
|
)
|
||||||
|
checkout.delete()
|
||||||
|
else:
|
||||||
|
for checkout in self.filter(document__in=queryset):
|
||||||
|
event_document_auto_check_in.commit(
|
||||||
|
target=checkout.document
|
||||||
|
)
|
||||||
|
checkout.delete()
|
||||||
|
|
||||||
|
|
||||||
class DocumentCheckoutManager(models.Manager):
|
class DocumentCheckoutManager(models.Manager):
|
||||||
def are_document_new_versions_allowed(self, document, user=None):
|
def are_document_new_versions_allowed(self, document, user=None):
|
||||||
try:
|
try:
|
||||||
@@ -27,25 +71,6 @@ class DocumentCheckoutManager(models.Manager):
|
|||||||
else:
|
else:
|
||||||
return not check_out_info.block_new_version
|
return not check_out_info.block_new_version
|
||||||
|
|
||||||
def check_in_document(self, document, user=None):
|
|
||||||
try:
|
|
||||||
document_check_out = self.model.objects.get(document=document)
|
|
||||||
except self.model.DoesNotExist:
|
|
||||||
raise DocumentNotCheckedOut
|
|
||||||
else:
|
|
||||||
with transaction.atomic():
|
|
||||||
if user:
|
|
||||||
if self.get_check_out_info(document=document).user != user:
|
|
||||||
event_document_forceful_check_in.commit(
|
|
||||||
actor=user, target=document
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
event_document_check_in.commit(actor=user, target=document)
|
|
||||||
else:
|
|
||||||
event_document_auto_check_in.commit(target=document)
|
|
||||||
|
|
||||||
document_check_out.delete()
|
|
||||||
|
|
||||||
def check_in_expired_check_outs(self):
|
def check_in_expired_check_outs(self):
|
||||||
for document in self.expired_check_outs():
|
for document in self.expired_check_outs():
|
||||||
document.check_in()
|
document.check_in()
|
||||||
@@ -57,7 +82,11 @@ class DocumentCheckoutManager(models.Manager):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def checked_out_documents(self):
|
def checked_out_documents(self):
|
||||||
return Document.objects.filter(
|
CheckedOutDocument = apps.get_model(
|
||||||
|
app_label='checkouts', model_name='CheckedOutDocument'
|
||||||
|
)
|
||||||
|
|
||||||
|
return CheckedOutDocument.objects.filter(
|
||||||
pk__in=self.model.objects.values('document__id')
|
pk__in=self.model.objects.values('document__id')
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,7 +103,11 @@ class DocumentCheckoutManager(models.Manager):
|
|||||||
return STATE_CHECKED_IN
|
return STATE_CHECKED_IN
|
||||||
|
|
||||||
def expired_check_outs(self):
|
def expired_check_outs(self):
|
||||||
expired_list = Document.objects.filter(
|
CheckedOutDocument = apps.get_model(
|
||||||
|
app_label='checkouts', model_name='CheckedOutDocument'
|
||||||
|
)
|
||||||
|
|
||||||
|
expired_list = CheckedOutDocument.objects.filter(
|
||||||
pk__in=self.model.objects.filter(
|
pk__in=self.model.objects.filter(
|
||||||
expiration_datetime__lte=now()
|
expiration_datetime__lte=now()
|
||||||
).values_list('document__pk', flat=True)
|
).values_list('document__pk', flat=True)
|
||||||
@@ -83,9 +116,6 @@ class DocumentCheckoutManager(models.Manager):
|
|||||||
return expired_list
|
return expired_list
|
||||||
|
|
||||||
def get_by_natural_key(self, document_natural_key):
|
def get_by_natural_key(self, document_natural_key):
|
||||||
Document = apps.get_model(
|
|
||||||
app_label='documents', model_name='Document'
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
document = Document.objects.get_by_natural_key(document_natural_key)
|
document = Document.objects.get_by_natural_key(document_natural_key)
|
||||||
except Document.DoesNotExist:
|
except Document.DoesNotExist:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ def method_check_in(self, user=None):
|
|||||||
app_label='checkouts', model_name='DocumentCheckout'
|
app_label='checkouts', model_name='DocumentCheckout'
|
||||||
)
|
)
|
||||||
|
|
||||||
return DocumentCheckout.objects.check_in_document(
|
return DocumentCheckout.business_logic.check_in_document(
|
||||||
document=self, user=user
|
document=self, user=user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ from mayan.apps.documents.models import Document
|
|||||||
|
|
||||||
from .events import event_document_check_out
|
from .events import event_document_check_out
|
||||||
from .exceptions import DocumentAlreadyCheckedOut
|
from .exceptions import DocumentAlreadyCheckedOut
|
||||||
from .managers import DocumentCheckoutManager, NewVersionBlockManager
|
from .managers import (
|
||||||
|
DocumentCheckoutBusinessLogicManager, DocumentCheckoutManager,
|
||||||
|
NewVersionBlockManager
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@ class DocumentCheckout(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
objects = DocumentCheckoutManager()
|
objects = DocumentCheckoutManager()
|
||||||
|
business_logic = DocumentCheckoutBusinessLogicManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('pk',)
|
ordering = ('pk',)
|
||||||
@@ -81,13 +85,13 @@ class DocumentCheckout(models.Model):
|
|||||||
natural_key.dependencies = ['documents.Document']
|
natural_key.dependencies = ['documents.Document']
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
new_checkout = not self.pk
|
is_new = not self.pk
|
||||||
if not new_checkout or self.document.is_checked_out():
|
if not is_new or self.document.is_checked_out():
|
||||||
raise DocumentAlreadyCheckedOut
|
raise DocumentAlreadyCheckedOut
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
result = super(DocumentCheckout, self).save(*args, **kwargs)
|
result = super(DocumentCheckout, self).save(*args, **kwargs)
|
||||||
if new_checkout:
|
if is_new:
|
||||||
event_document_check_out.commit(
|
event_document_check_out.commit(
|
||||||
actor=self.user, target=self.document
|
actor=self.user, target=self.document
|
||||||
)
|
)
|
||||||
@@ -119,3 +123,24 @@ class NewVersionBlock(models.Model):
|
|||||||
def natural_key(self):
|
def natural_key(self):
|
||||||
return self.document.natural_key()
|
return self.document.natural_key()
|
||||||
natural_key.dependencies = ['documents.Document']
|
natural_key.dependencies = ['documents.Document']
|
||||||
|
|
||||||
|
|
||||||
|
class CheckedOutDocument(Document):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
def get_user_display(self):
|
||||||
|
check_out_info = self.get_check_out_info()
|
||||||
|
return check_out_info.user.get_full_name() or check_out_info.user
|
||||||
|
|
||||||
|
get_user_display.short_description = _('User')
|
||||||
|
|
||||||
|
def get_checkout_datetime(self):
|
||||||
|
return self.get_check_out_info().checkout_datetime
|
||||||
|
|
||||||
|
get_checkout_datetime.short_description = _('Checkout time and date')
|
||||||
|
|
||||||
|
def get_checkout_expiration(self):
|
||||||
|
return self.get_check_out_info().expiration_datetime
|
||||||
|
|
||||||
|
get_checkout_expiration.short_description = _('Checkout expiration')
|
||||||
|
|||||||
@@ -4,13 +4,19 @@ import datetime
|
|||||||
|
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
from mayan.apps.common.literals import TIME_DELTA_UNIT_DAYS
|
||||||
|
from mayan.apps.common.tests.utils import as_id_list
|
||||||
|
|
||||||
from ..models import DocumentCheckout
|
from ..models import DocumentCheckout
|
||||||
|
|
||||||
|
|
||||||
class DocumentCheckoutTestMixin(object):
|
class DocumentCheckoutTestMixin(object):
|
||||||
_test_document_check_out_seconds = 0.1
|
_test_document_check_out_seconds = 0.1
|
||||||
|
|
||||||
def _check_out_test_document(self, user=None):
|
def _check_out_test_document(self, document=None, user=None):
|
||||||
|
if not document:
|
||||||
|
document = self.test_document
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
user = self._test_case_user
|
user = self._test_case_user
|
||||||
|
|
||||||
@@ -19,7 +25,61 @@ class DocumentCheckoutTestMixin(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.test_check_out = DocumentCheckout.objects.check_out_document(
|
self.test_check_out = DocumentCheckout.objects.check_out_document(
|
||||||
block_new_version=True, document=self.test_document,
|
block_new_version=True, document=document,
|
||||||
expiration_datetime=self._check_out_expiration_datetime,
|
expiration_datetime=self._check_out_expiration_datetime,
|
||||||
user=user
|
user=user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentCheckoutViewTestMixin(object):
|
||||||
|
def _request_test_document_check_in_get_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='checkouts:check_in_document', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_check_in_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='checkouts:check_in_document', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_multiple_check_in_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='checkouts:check_in_document_multiple', data={
|
||||||
|
'id_list': as_id_list(items=self.test_documents)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_check_out_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='checkouts:check_out_document', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}, data={
|
||||||
|
'block_new_version': True,
|
||||||
|
'expiration_datetime_0': TIME_DELTA_UNIT_DAYS,
|
||||||
|
'expiration_datetime_1': 2
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_multiple_check_out_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='checkouts:check_out_document_multiple', data={
|
||||||
|
'block_new_version': True,
|
||||||
|
'expiration_datetime_0': TIME_DELTA_UNIT_DAYS,
|
||||||
|
'expiration_datetime_1': 2,
|
||||||
|
'id_list': as_id_list(items=self.test_documents)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_check_out_detail_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='checkouts:check_out_info', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_check_out_list_view(self):
|
||||||
|
return self.get(viewname='checkouts:check_out_list')
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class CheckoutsAPITestCase(DocumentCheckoutTestMixin, DocumentTestMixin, BaseAPI
|
|||||||
force_text(self.test_document.uuid)
|
force_text(self.test_document.uuid)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_document_checkout_view(self):
|
def _request_test_document_check_out_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='rest_api:checkout-document-list', data={
|
viewname='rest_api:checkout-document-list', data={
|
||||||
'document_pk': self.test_document.pk,
|
'document_pk': self.test_document.pk,
|
||||||
@@ -74,7 +74,7 @@ class CheckoutsAPITestCase(DocumentCheckoutTestMixin, DocumentTestMixin, BaseAPI
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_document_checkout_no_access(self):
|
def test_document_checkout_no_access(self):
|
||||||
response = self._request_document_checkout_view()
|
response = self._request_test_document_check_out_view()
|
||||||
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertEqual(DocumentCheckout.objects.count(), 0)
|
self.assertEqual(DocumentCheckout.objects.count(), 0)
|
||||||
@@ -82,7 +82,7 @@ class CheckoutsAPITestCase(DocumentCheckoutTestMixin, DocumentTestMixin, BaseAPI
|
|||||||
def test_document_checkout_with_access(self):
|
def test_document_checkout_with_access(self):
|
||||||
self.grant_access(permission=permission_document_check_out, obj=self.test_document)
|
self.grant_access(permission=permission_document_check_out, obj=self.test_document)
|
||||||
|
|
||||||
response = self._request_document_checkout_view()
|
response = self._request_test_document_check_out_view()
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ from mayan.apps.documents.tests import GenericDocumentTestCase, DocumentTestMixi
|
|||||||
from mayan.apps.documents.tests.literals import TEST_SMALL_DOCUMENT_PATH
|
from mayan.apps.documents.tests.literals import TEST_SMALL_DOCUMENT_PATH
|
||||||
|
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
DocumentAlreadyCheckedOut, DocumentNotCheckedOut,
|
DocumentAlreadyCheckedOut, NewDocumentVersionNotAllowed
|
||||||
NewDocumentVersionNotAllowed
|
|
||||||
)
|
)
|
||||||
from ..models import DocumentCheckout, NewVersionBlock
|
from ..models import DocumentCheckout, NewVersionBlock
|
||||||
|
|
||||||
@@ -49,10 +48,6 @@ class DocumentCheckoutTestCase(DocumentCheckoutTestMixin, GenericDocumentTestCas
|
|||||||
block_new_version=True
|
block_new_version=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_checkin_without_checkout(self):
|
|
||||||
with self.assertRaises(DocumentNotCheckedOut):
|
|
||||||
self.test_document.check_in()
|
|
||||||
|
|
||||||
def test_auto_check_in(self):
|
def test_auto_check_in(self):
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from mayan.apps.common.literals import TIME_DELTA_UNIT_DAYS
|
|
||||||
from mayan.apps.documents.permissions import permission_document_view
|
from mayan.apps.documents.permissions import permission_document_view
|
||||||
from mayan.apps.documents.tests import GenericDocumentViewTestCase
|
from mayan.apps.documents.tests import GenericDocumentViewTestCase
|
||||||
from mayan.apps.sources.links import link_document_version_upload
|
from mayan.apps.sources.links import link_document_version_upload
|
||||||
@@ -12,64 +11,53 @@ from ..permissions import (
|
|||||||
permission_document_check_out, permission_document_check_out_detail_view
|
permission_document_check_out, permission_document_check_out_detail_view
|
||||||
)
|
)
|
||||||
|
|
||||||
from .mixins import DocumentCheckoutTestMixin
|
from .mixins import DocumentCheckoutTestMixin, DocumentCheckoutViewTestMixin
|
||||||
|
|
||||||
|
|
||||||
class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentViewTestCase):
|
class DocumentCheckoutViewTestCase(
|
||||||
def _request_document_check_in_get_view(self):
|
DocumentCheckoutTestMixin, DocumentCheckoutViewTestMixin,
|
||||||
return self.get(
|
GenericDocumentViewTestCase
|
||||||
viewname='checkouts:check_in_document', kwargs={
|
):
|
||||||
'pk': self.test_document.pk
|
def test_document_check_in_get_view_no_permission(self):
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_check_in_document_get_view_no_permission(self):
|
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
response = self._request_document_check_in_get_view()
|
response = self._request_test_document_check_in_get_view()
|
||||||
self.assertContains(
|
self.assertNotContains(
|
||||||
response=response, text=self.test_document.label, status_code=200
|
response=response, text=self.test_document.label, status_code=404
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(self.test_document.is_checked_out())
|
self.assertTrue(self.test_document.is_checked_out())
|
||||||
|
|
||||||
def test_check_in_document_get_view_with_access(self):
|
def test_document_check_in_get_view_with_access(self):
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_document, permission=permission_document_check_in
|
obj=self.test_document, permission=permission_document_check_in
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_document_check_in_get_view()
|
response = self._request_test_document_check_in_get_view()
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response=response, text=self.test_document.label, status_code=200
|
response=response, text=self.test_document.label, status_code=200
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(self.test_document.is_checked_out())
|
self.assertTrue(self.test_document.is_checked_out())
|
||||||
|
|
||||||
def _request_document_check_in_post_view(self):
|
def test_document_check_in_post_view_no_permission(self):
|
||||||
return self.post(
|
|
||||||
viewname='checkouts:check_in_document', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_check_in_document_post_view_no_permission(self):
|
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
response = self._request_document_check_in_post_view()
|
response = self._request_test_document_check_in_post_view()
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
self.assertTrue(self.test_document.is_checked_out())
|
self.assertTrue(self.test_document.is_checked_out())
|
||||||
|
|
||||||
def test_check_in_document_post_view_with_access(self):
|
def test_document_check_in_post_view_with_access(self):
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_document, permission=permission_document_check_in
|
obj=self.test_document, permission=permission_document_check_in
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_document_check_in_post_view()
|
response = self._request_test_document_check_in_post_view()
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
self.assertFalse(self.test_document.is_checked_out())
|
self.assertFalse(self.test_document.is_checked_out())
|
||||||
@@ -79,24 +67,93 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_document_checkout_view(self):
|
def test_document_multiple_check_in_post_view_no_permission(self):
|
||||||
return self.post(
|
# Upload second document
|
||||||
viewname='checkouts:check_out_document', kwargs={
|
self.upload_document()
|
||||||
'pk': self.test_document.pk
|
|
||||||
}, data={
|
self._check_out_test_document(document=self.test_documents[0])
|
||||||
'expiration_datetime_0': 2,
|
self._check_out_test_document(document=self.test_documents[1])
|
||||||
'expiration_datetime_1': TIME_DELTA_UNIT_DAYS,
|
|
||||||
'block_new_version': True
|
response = self._request_test_document_multiple_check_in_post_view()
|
||||||
}
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
self.assertTrue(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertTrue(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_check_out_document_view_no_permission(self):
|
def test_document_multiple_check_in_post_view_with_document_0_access(self):
|
||||||
response = self._request_document_checkout_view()
|
# Upload second document
|
||||||
self.assertEqual(response.status_code, 403)
|
self.upload_document()
|
||||||
|
|
||||||
|
self._check_out_test_document(document=self.test_documents[0])
|
||||||
|
self._check_out_test_document(document=self.test_documents[1])
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0], permission=permission_document_check_in
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_multiple_check_in_post_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertFalse(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertTrue(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_multiple_check_in_post_view_with_access(self):
|
||||||
|
# Upload second document
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self._check_out_test_document(document=self.test_documents[0])
|
||||||
|
self._check_out_test_document(document=self.test_documents[1])
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0], permission=permission_document_check_in
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[1], permission=permission_document_check_in
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_multiple_check_in_post_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertFalse(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertFalse(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_check_out_view_no_permission(self):
|
||||||
|
response = self._request_test_document_check_out_view()
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
self.assertFalse(self.test_document.is_checked_out())
|
self.assertFalse(self.test_document.is_checked_out())
|
||||||
|
|
||||||
def test_check_out_document_view_with_access(self):
|
def test_document_check_out_view_with_access(self):
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_document, permission=permission_document_check_out
|
obj=self.test_document, permission=permission_document_check_out
|
||||||
)
|
)
|
||||||
@@ -105,28 +162,117 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie
|
|||||||
permission=permission_document_check_out_detail_view
|
permission=permission_document_check_out_detail_view
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_document_checkout_view()
|
response = self._request_test_document_check_out_view()
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
self.assertTrue(self.test_document.is_checked_out())
|
self.assertTrue(self.test_document.is_checked_out())
|
||||||
|
|
||||||
def _request_check_out_detail_view(self):
|
def test_document_multiple_check_out_post_view_no_permission(self):
|
||||||
return self.get(
|
# Upload second document
|
||||||
viewname='checkouts:check_out_info', kwargs={
|
self.upload_document()
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[1],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_checkout_detail_view_no_permission(self):
|
response = self._request_test_document_multiple_check_out_post_view()
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
self.assertFalse(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertFalse(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_multiple_check_out_post_view_with_document_0_access(self):
|
||||||
|
# Upload second document
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0], permission=permission_document_check_out
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[1],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_multiple_check_out_post_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertTrue(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertFalse(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_multiple_check_out_post_view_with_access(self):
|
||||||
|
# Upload second document
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0], permission=permission_document_check_out
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[1], permission=permission_document_check_out
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[1],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_multiple_check_out_post_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertTrue(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertTrue(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_check_out_detail_view_no_permission(self):
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
response = self._request_check_out_detail_view()
|
response = self._request_test_document_check_out_detail_view()
|
||||||
|
|
||||||
self.assertNotContains(
|
self.assertNotContains(
|
||||||
response, text=STATE_LABELS[STATE_CHECKED_OUT], status_code=404
|
response, text=STATE_LABELS[STATE_CHECKED_OUT], status_code=404
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_checkout_detail_view_with_access(self):
|
def test_document_check_out_detail_view_with_access(self):
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
@@ -134,15 +280,12 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie
|
|||||||
permission=permission_document_check_out_detail_view
|
permission=permission_document_check_out_detail_view
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_check_out_detail_view()
|
response = self._request_test_document_check_out_detail_view()
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response, text=STATE_LABELS[STATE_CHECKED_OUT], status_code=200
|
response, text=STATE_LABELS[STATE_CHECKED_OUT], status_code=200
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_check_out_list_view(self):
|
def test_document_checkout_list_view_no_permission(self):
|
||||||
return self.get(viewname='checkouts:check_out_list')
|
|
||||||
|
|
||||||
def test_checkout_list_view_no_permission(self):
|
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
@@ -150,12 +293,12 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie
|
|||||||
permission=permission_document_view
|
permission=permission_document_view
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_check_out_list_view()
|
response = self._request_test_document_check_out_list_view()
|
||||||
self.assertNotContains(
|
self.assertNotContains(
|
||||||
response=response, text=self.test_document.label, status_code=200
|
response=response, text=self.test_document.label, status_code=200
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_checkout_list_view_with_access(self):
|
def test_document_checkout_list_view_with_access(self):
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
@@ -167,12 +310,12 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie
|
|||||||
permission=permission_document_view
|
permission=permission_document_view
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_check_out_list_view()
|
response = self._request_test_document_check_out_list_view()
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response=response, text=self.test_document.label, status_code=200
|
response=response, text=self.test_document.label, status_code=200
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_document_new_version_after_check_out(self):
|
def test_document_check_out_new_version(self):
|
||||||
"""
|
"""
|
||||||
Gitlab issue #231
|
Gitlab issue #231
|
||||||
User shown option to upload new version of a document even though it
|
User shown option to upload new version of a document even though it
|
||||||
@@ -209,45 +352,39 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie
|
|||||||
|
|
||||||
self.assertEqual(resolved_link, None)
|
self.assertEqual(resolved_link, None)
|
||||||
|
|
||||||
def test_forcefull_check_in_document_view_no_permission(self):
|
def test_document_check_in_forcefull_view_no_permission(self):
|
||||||
# Gitlab issue #237
|
# Gitlab issue #237
|
||||||
# Forcefully checking in a document by a user without adequate
|
# Forcefully checking in a document by a user without adequate
|
||||||
# permissions throws out an error
|
# permissions throws out an error
|
||||||
|
|
||||||
self._create_test_case_superuser()
|
self._create_test_user()
|
||||||
self._check_out_test_document(user=self._test_case_superuser)
|
self._check_out_test_document(user=self.test_user)
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_document, permission=permission_document_check_in
|
obj=self.test_document, permission=permission_document_check_in
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self.post(
|
|
||||||
viewname='checkouts:check_in_document', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.assertContains(
|
|
||||||
response=response, text='Insufficient permissions', status_code=403
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(self.test_document.is_checked_out())
|
|
||||||
|
|
||||||
def test_forcefull_check_in_document_view_with_permission(self):
|
|
||||||
self._create_test_case_superuser()
|
|
||||||
self._check_out_test_document(user=self._test_case_superuser)
|
|
||||||
|
|
||||||
self.grant_access(
|
|
||||||
obj=self.test_document, permission=permission_document_check_in
|
|
||||||
)
|
|
||||||
self.grant_access(
|
|
||||||
obj=self.test_document, permission=permission_document_check_in_override
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self.post(
|
response = self.post(
|
||||||
viewname='checkouts:check_in_document', kwargs={
|
viewname='checkouts:check_in_document', kwargs={
|
||||||
'pk': self.test_document.pk
|
'pk': self.test_document.pk
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertTrue(self.test_document.is_checked_out())
|
||||||
|
|
||||||
|
def test_document_check_in_forcefull_view_with_access(self):
|
||||||
|
self._create_test_user()
|
||||||
|
self._check_out_test_document(user=self.test_user)
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_check_in_override
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.post(
|
||||||
|
viewname='checkouts:check_in_document', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertFalse(self.test_document.is_checked_out())
|
self.assertFalse(self.test_document.is_checked_out())
|
||||||
|
|||||||
@@ -4,25 +4,34 @@ from django.conf.urls import url
|
|||||||
|
|
||||||
from .api_views import APICheckedoutDocumentListView, APICheckedoutDocumentView
|
from .api_views import APICheckedoutDocumentListView, APICheckedoutDocumentView
|
||||||
from .views import (
|
from .views import (
|
||||||
CheckoutDocumentView, CheckoutDetailView, CheckoutListView,
|
DocumentCheckinView, DocumentCheckoutDetailView, DocumentCheckoutView,
|
||||||
DocumentCheckinView
|
DocumentCheckoutListView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(
|
url(
|
||||||
regex=r'^list/$', view=CheckoutListView.as_view(), name='check_out_list'
|
regex=r'^documents/$', view=DocumentCheckoutListView.as_view(),
|
||||||
|
name='check_out_list'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^(?P<pk>\d+)/check/out/$', view=CheckoutDocumentView.as_view(),
|
regex=r'^documents/(?P<pk>\d+)/check_in/$', view=DocumentCheckinView.as_view(),
|
||||||
name='check_out_document'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^(?P<pk>\d+)/check/in/$', view=DocumentCheckinView.as_view(),
|
|
||||||
name='check_in_document'
|
name='check_in_document'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^(?P<pk>\d+)/check/info/$', view=CheckoutDetailView.as_view(),
|
regex=r'^documents/multiple/check_in/$',
|
||||||
name='check_out_info'
|
name='check_in_document_multiple', view=DocumentCheckinView.as_view()
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<pk>\d+)/check_out/$', view=DocumentCheckoutView.as_view(),
|
||||||
|
name='check_out_document'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/multiple/check_out/$',
|
||||||
|
name='check_out_document_multiple', view=DocumentCheckoutView.as_view()
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<pk>\d+)/checkout/info/$',
|
||||||
|
view=DocumentCheckoutDetailView.as_view(), name='check_out_info'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _, ungettext
|
||||||
|
|
||||||
from mayan.apps.acls.models import AccessControlList
|
from mayan.apps.acls.models import AccessControlList
|
||||||
from mayan.apps.common.generics import (
|
from mayan.apps.common.generics import (
|
||||||
ConfirmView, SingleObjectCreateView, SingleObjectDetailView
|
MultipleObjectConfirmActionView, MultipleObjectFormActionView,
|
||||||
|
SingleObjectDetailView
|
||||||
)
|
)
|
||||||
from mayan.apps.common.utils import encapsulate
|
|
||||||
from mayan.apps.documents.models import Document
|
from mayan.apps.documents.models import Document
|
||||||
from mayan.apps.documents.views import DocumentListView
|
from mayan.apps.documents.views import DocumentListView
|
||||||
|
|
||||||
from .exceptions import DocumentAlreadyCheckedOut, DocumentNotCheckedOut
|
|
||||||
from .forms import DocumentCheckoutForm, DocumentCheckoutDefailForm
|
from .forms import DocumentCheckoutForm, DocumentCheckoutDefailForm
|
||||||
from .icons import icon_check_out_info
|
from .icons import icon_check_out_info
|
||||||
from .models import DocumentCheckout
|
from .models import DocumentCheckout
|
||||||
@@ -24,159 +20,124 @@ from .permissions import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentCheckinView(ConfirmView):
|
class DocumentCheckinView(MultipleObjectConfirmActionView):
|
||||||
def get_extra_context(self):
|
error_message = 'Unable to check in document "%(instance)s". %(exception)s'
|
||||||
document = self.get_object()
|
model = Document
|
||||||
|
pk_url_kwarg = 'pk'
|
||||||
context = {
|
success_message_singular = '%(count)d document checked in.'
|
||||||
'object': document,
|
success_message_plural = '%(count)d documents checked in.'
|
||||||
}
|
|
||||||
|
|
||||||
if document.get_check_out_info().user != self.request.user:
|
|
||||||
context['title'] = _(
|
|
||||||
'You didn\'t originally checked out this document. '
|
|
||||||
'Forcefully check in the document: %s?'
|
|
||||||
) % document
|
|
||||||
else:
|
|
||||||
context['title'] = _('Check in the document: %s?') % document
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
return get_object_or_404(klass=Document, pk=self.kwargs['pk'])
|
|
||||||
|
|
||||||
def get_post_action_redirect(self):
|
|
||||||
return reverse(
|
|
||||||
viewname='checkouts:check_out_info', kwargs={
|
|
||||||
'pk': self.get_object().pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def view_action(self):
|
|
||||||
document = self.get_object()
|
|
||||||
|
|
||||||
if document.get_check_out_info().user == self.request.user:
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
obj=document, permissions=(permission_document_check_in,),
|
|
||||||
user=self.request.user
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
obj=document,
|
|
||||||
permissions=(permission_document_check_in_override,),
|
|
||||||
user=self.request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
document.check_in(user=self.request.user)
|
|
||||||
except DocumentNotCheckedOut:
|
|
||||||
messages.error(
|
|
||||||
message=_('Document has not been checked out.'),
|
|
||||||
request=self.request
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
messages.success(
|
|
||||||
message=_(
|
|
||||||
'Document "%s" checked in successfully.'
|
|
||||||
) % document, request=self.request
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CheckoutDocumentView(SingleObjectCreateView):
|
|
||||||
form_class = DocumentCheckoutForm
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
self.document = get_object_or_404(klass=Document, pk=self.kwargs['pk'])
|
|
||||||
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
obj=self.document, permissions=(permission_document_check_out,),
|
|
||||||
user=request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
return super(
|
|
||||||
CheckoutDocumentView, self
|
|
||||||
).dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
try:
|
|
||||||
instance = form.save(commit=False)
|
|
||||||
instance.user = self.request.user
|
|
||||||
instance.document = self.document
|
|
||||||
instance.save()
|
|
||||||
except DocumentAlreadyCheckedOut:
|
|
||||||
messages.error(
|
|
||||||
message=_('Document already checked out.'),
|
|
||||||
request=self.request
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
messages.success(
|
|
||||||
message=_(
|
|
||||||
'Document "%s" checked out successfully.'
|
|
||||||
) % self.document, request=self.request
|
|
||||||
)
|
|
||||||
|
|
||||||
return HttpResponseRedirect(redirect_to=self.get_success_url())
|
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
queryset = self.get_object_list()
|
||||||
'object': self.document,
|
|
||||||
'title': _('Check out document: %s') % self.document
|
result = {
|
||||||
|
'title': ungettext(
|
||||||
|
singular='Check in %(count)d document',
|
||||||
|
plural='Check in %(count)d documents',
|
||||||
|
number=queryset.count()
|
||||||
|
) % {
|
||||||
|
'count': queryset.count(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_post_action_redirect(self):
|
if queryset.count() == 1:
|
||||||
return reverse(
|
result.update(
|
||||||
viewname='checkouts:check_out_info', kwargs={
|
{
|
||||||
'pk': self.document.pk
|
'object': queryset.first(),
|
||||||
}
|
'title': _(
|
||||||
)
|
'Check in document: %s'
|
||||||
|
) % queryset.first()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
class CheckoutListView(DocumentListView):
|
def get_post_object_action_url(self):
|
||||||
def get_document_queryset(self):
|
if self.action_count == 1:
|
||||||
return AccessControlList.objects.restrict_queryset(
|
return reverse(
|
||||||
permission=permission_document_check_out_detail_view,
|
viewname='checkouts:document_checkout_info',
|
||||||
queryset=DocumentCheckout.objects.checked_out_documents(),
|
kwargs={'pk': self.action_id_list[0]}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
super(DocumentCheckinView, self).get_post_action_redirect()
|
||||||
|
|
||||||
|
def get_source_queryset(self):
|
||||||
|
# object_permission is None to disable restricting queryset mixin
|
||||||
|
# and restrict the queryset ourselves from two permissions
|
||||||
|
|
||||||
|
source_queryset = super(DocumentCheckinView, self).get_source_queryset()
|
||||||
|
|
||||||
|
check_in_queryset = AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission_document_check_in, queryset=source_queryset,
|
||||||
user=self.request.user
|
user=self.request.user
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_extra_context(self):
|
check_in_override_queryset = AccessControlList.objects.restrict_queryset(
|
||||||
context = super(CheckoutListView, self).get_extra_context()
|
permission=permission_document_check_in_override,
|
||||||
context.update(
|
queryset=source_queryset, user=self.request.user
|
||||||
{
|
)
|
||||||
'extra_columns': (
|
|
||||||
{
|
return check_in_queryset | check_in_override_queryset
|
||||||
'name': _('User'),
|
|
||||||
'attribute': encapsulate(
|
def object_action(self, form, instance):
|
||||||
lambda document: document.get_check_out_info().user.get_full_name() or document.get_check_out_info().user
|
DocumentCheckout.business_logic.check_in_document(
|
||||||
)
|
document=instance, user=self.request.user
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': _('Checkout time and date'),
|
|
||||||
'attribute': encapsulate(
|
|
||||||
lambda document: document.get_check_out_info().checkout_datetime
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': _('Checkout expiration'),
|
|
||||||
'attribute': encapsulate(
|
|
||||||
lambda document: document.get_check_out_info().expiration_datetime
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
'no_results_icon': icon_check_out_info,
|
|
||||||
'no_results_text': _(
|
|
||||||
'Checking out a document blocks certain document '
|
|
||||||
'operations for a predetermined amount of '
|
|
||||||
'time.'
|
|
||||||
),
|
|
||||||
'no_results_title': _('No documents have been checked out'),
|
|
||||||
'title': _('Documents checked out'),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class CheckoutDetailView(SingleObjectDetailView):
|
class DocumentCheckoutView(MultipleObjectFormActionView):
|
||||||
|
error_message = 'Unable to checkout document "%(instance)s". %(exception)s'
|
||||||
|
form_class = DocumentCheckoutForm
|
||||||
|
model = Document
|
||||||
|
object_permission = permission_document_check_out
|
||||||
|
pk_url_kwarg = 'pk'
|
||||||
|
success_message_singular = '%(count)d document checked out.'
|
||||||
|
success_message_plural = '%(count)d documents checked out.'
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
queryset = self.get_object_list()
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'title': ungettext(
|
||||||
|
singular='Checkout %(count)d document',
|
||||||
|
plural='Checkout %(count)d documents',
|
||||||
|
number=queryset.count()
|
||||||
|
) % {
|
||||||
|
'count': queryset.count(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryset.count() == 1:
|
||||||
|
result.update(
|
||||||
|
{
|
||||||
|
'object': queryset.first(),
|
||||||
|
'title': _(
|
||||||
|
'Check out document: %s'
|
||||||
|
) % queryset.first()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_post_object_action_url(self):
|
||||||
|
if self.action_count == 1:
|
||||||
|
return reverse(
|
||||||
|
viewname='checkouts:document_checkout_info',
|
||||||
|
kwargs={'pk': self.action_id_list[0]}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
super(DocumentCheckoutView, self).get_post_action_redirect()
|
||||||
|
|
||||||
|
def object_action(self, form, instance):
|
||||||
|
DocumentCheckout.objects.check_out_document(
|
||||||
|
block_new_version=form.cleaned_data['block_new_version'],
|
||||||
|
document=instance,
|
||||||
|
expiration_datetime=form.cleaned_data['expiration_datetime'],
|
||||||
|
user=self.request.user,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentCheckoutDetailView(SingleObjectDetailView):
|
||||||
form_class = DocumentCheckoutDefailForm
|
form_class = DocumentCheckoutDefailForm
|
||||||
model = Document
|
model = Document
|
||||||
object_permission = permission_document_check_out_detail_view
|
object_permission = permission_document_check_out_detail_view
|
||||||
@@ -188,3 +149,27 @@ class CheckoutDetailView(SingleObjectDetailView):
|
|||||||
'Check out details for document: %s'
|
'Check out details for document: %s'
|
||||||
) % self.object
|
) % self.object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentCheckoutListView(DocumentListView):
|
||||||
|
def get_document_queryset(self):
|
||||||
|
return AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission_document_check_out_detail_view,
|
||||||
|
queryset=DocumentCheckout.objects.checked_out_documents(),
|
||||||
|
user=self.request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
context = super(DocumentCheckoutListView, self).get_extra_context()
|
||||||
|
context.update(
|
||||||
|
{
|
||||||
|
'no_results_icon': icon_check_out_info,
|
||||||
|
'no_results_text': _(
|
||||||
|
'Checking out a document, blocks certain operations '
|
||||||
|
'for a predetermined amount of time.'
|
||||||
|
),
|
||||||
|
'no_results_title': _('No documents have been checked out'),
|
||||||
|
'title': _('Checked out documents'),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return context
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ class SplitTimeDeltaWidget(forms.widgets.MultiWidget):
|
|||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
def value_from_datadict(self, querydict, files, name):
|
def value_from_datadict(self, querydict, files, name):
|
||||||
unit = querydict.get('{}_1'.format(name))
|
unit = querydict.get('{}_0'.format(name))
|
||||||
period = querydict.get('{}_0'.format(name))
|
period = querydict.get('{}_1'.format(name))
|
||||||
|
|
||||||
if not unit or not period:
|
if not unit or not period:
|
||||||
return now()
|
return now()
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ from .links import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .literals import MESSAGE_SQLITE_WARNING
|
from .literals import MESSAGE_SQLITE_WARNING
|
||||||
from .menus import (
|
from .menus import menu_about, menu_secondary, menu_topbar, menu_user
|
||||||
menu_about, menu_main, menu_secondary, menu_topbar, menu_user
|
|
||||||
)
|
|
||||||
from .settings import (
|
from .settings import (
|
||||||
setting_auto_logging, setting_production_error_log_path,
|
setting_auto_logging, setting_production_error_log_path,
|
||||||
setting_production_error_logging
|
setting_production_error_logging
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import errno
|
|
||||||
import os
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from pathlib2 import Path
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core import management
|
|
||||||
from django.core.management.base import CommandError
|
|
||||||
from django.utils.encoding import force_text
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from mayan.apps.documents.models import DocumentType
|
|
||||||
from mayan.apps.storage.utils import fs_cleanup
|
|
||||||
|
|
||||||
from ...literals import MESSAGE_DEPRECATION_WARNING
|
|
||||||
from ...warnings import DeprecationWarning
|
|
||||||
|
|
||||||
CONVERTDB_FOLDER = 'convertdb'
|
|
||||||
CONVERTDB_OUTPUT_FILENAME = 'migrate.json'
|
|
||||||
|
|
||||||
|
|
||||||
class Command(management.BaseCommand):
|
|
||||||
help = 'Convert from a database backend to another one.'
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
warnings.warn(
|
|
||||||
category=DeprecationWarning,
|
|
||||||
message=force_text(MESSAGE_DEPRECATION_WARNING)
|
|
||||||
)
|
|
||||||
|
|
||||||
super(Command, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument(
|
|
||||||
'args', metavar='app_label[.ModelName]', nargs='*',
|
|
||||||
help=_(
|
|
||||||
'Restricts dumped data to the specified app_label or '
|
|
||||||
'app_label.ModelName.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--from', action='store', default='default', dest='from',
|
|
||||||
help=_(
|
|
||||||
'The database from which data will be exported. If omitted '
|
|
||||||
'the database named "default" will be used.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--to', action='store', default='default', dest='to',
|
|
||||||
help=_(
|
|
||||||
'The database to which data will be imported. If omitted '
|
|
||||||
'the database named "default" will be used.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--force', action='store_true', dest='force',
|
|
||||||
help=_(
|
|
||||||
'Force the conversion of the database even if the receiving '
|
|
||||||
'database is not empty.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, *app_labels, **options):
|
|
||||||
# Create the media/convertdb folder
|
|
||||||
convertdb_folder_path = force_text(
|
|
||||||
Path(
|
|
||||||
settings.MEDIA_ROOT, CONVERTDB_FOLDER
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.makedirs(convertdb_folder_path)
|
|
||||||
except OSError as exception:
|
|
||||||
if exception.errno == errno.EEXIST:
|
|
||||||
pass
|
|
||||||
|
|
||||||
convertdb_file_path = force_text(
|
|
||||||
Path(
|
|
||||||
convertdb_folder_path, CONVERTDB_OUTPUT_FILENAME
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
management.call_command(command_name='purgeperiodictasks')
|
|
||||||
|
|
||||||
management.call_command(
|
|
||||||
'dumpdata', *app_labels, all=True,
|
|
||||||
database=options['from'], natural_primary=True,
|
|
||||||
natural_foreign=True, output=convertdb_file_path,
|
|
||||||
interactive=False, format='json'
|
|
||||||
)
|
|
||||||
|
|
||||||
if DocumentType.objects.using(options['to']).count() and not options['force']:
|
|
||||||
fs_cleanup(convertdb_file_path)
|
|
||||||
raise CommandError(
|
|
||||||
'There is existing data in the database that will be '
|
|
||||||
'used for the import. If you proceed with the conversion '
|
|
||||||
'you might lose data. Please check your settings.'
|
|
||||||
)
|
|
||||||
|
|
||||||
management.call_command(
|
|
||||||
'loaddata', convertdb_file_path, database=options['to'], interactive=False,
|
|
||||||
verbosity=3
|
|
||||||
)
|
|
||||||
fs_cleanup(convertdb_file_path)
|
|
||||||
32
mayan/apps/common/migrations/0012_auto_20190711_0548.py
Normal file
32
mayan/apps/common/migrations/0012_auto_20190711_0548.py
Normal file
File diff suppressed because one or more lines are too long
22
mayan/apps/common/serialization.py
Normal file
22
mayan/apps/common/serialization.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
try:
|
||||||
|
from yaml import CSafeLoader as SafeLoader, CSafeDumper as SafeDumper
|
||||||
|
except ImportError:
|
||||||
|
from yaml import SafeLoader, SafeDumper
|
||||||
|
|
||||||
|
|
||||||
|
def yaml_dump(*args, **kwargs):
|
||||||
|
defaults = {'Dumper': SafeDumper}
|
||||||
|
defaults.update(kwargs)
|
||||||
|
|
||||||
|
return yaml.dump(*args, **defaults)
|
||||||
|
|
||||||
|
|
||||||
|
def yaml_load(*args, **kwargs):
|
||||||
|
defaults = {'Loader': SafeLoader}
|
||||||
|
defaults.update(kwargs)
|
||||||
|
|
||||||
|
return yaml.load(*args, **defaults)
|
||||||
@@ -6,11 +6,10 @@ from django.conf import settings
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
import mayan
|
import mayan
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
from .literals import DEFAULT_COMMON_HOME_VIEW
|
from .literals import DEFAULT_COMMON_HOME_VIEW
|
||||||
|
|
||||||
|
|
||||||
namespace = Namespace(label=_('Common'), name='common')
|
namespace = Namespace(label=_('Common'), name='common')
|
||||||
|
|
||||||
setting_auto_logging = namespace.add_setting(
|
setting_auto_logging = namespace.add_setting(
|
||||||
@@ -95,322 +94,5 @@ setting_shared_storage = namespace.add_setting(
|
|||||||
)
|
)
|
||||||
setting_shared_storage_arguments = namespace.add_setting(
|
setting_shared_storage_arguments = namespace.add_setting(
|
||||||
global_name='COMMON_SHARED_STORAGE_ARGUMENTS',
|
global_name='COMMON_SHARED_STORAGE_ARGUMENTS',
|
||||||
default='{{location: {}}}'.format(
|
default={'location': os.path.join(settings.MEDIA_ROOT, 'shared_files')}
|
||||||
os.path.join(settings.MEDIA_ROOT, 'shared_files')
|
|
||||||
), quoted=True
|
|
||||||
)
|
|
||||||
|
|
||||||
namespace = Namespace(label=_('Django'), name='django')
|
|
||||||
|
|
||||||
setting_django_allowed_hosts = namespace.add_setting(
|
|
||||||
global_name='ALLOWED_HOSTS', default=settings.ALLOWED_HOSTS,
|
|
||||||
help_text=_(
|
|
||||||
'A list of strings representing the host/domain names that this site '
|
|
||||||
'can serve. This is a security measure to prevent HTTP Host header '
|
|
||||||
'attacks, which are possible even under many seemingly-safe web '
|
|
||||||
'server configurations. Values in this list can be '
|
|
||||||
'fully qualified names (e.g. \'www.example.com\'), in which case '
|
|
||||||
'they will be matched against the request\'s Host header exactly '
|
|
||||||
'(case-insensitive, not including port). A value beginning with a '
|
|
||||||
'period can be used as a subdomain wildcard: \'.example.com\' will '
|
|
||||||
'match example.com, www.example.com, and any other subdomain of '
|
|
||||||
'example.com. A value of \'*\' will match anything; in this case you '
|
|
||||||
'are responsible to provide your own validation of the Host header '
|
|
||||||
'(perhaps in a middleware; if so this middleware must be listed '
|
|
||||||
'first in MIDDLEWARE).'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_append_slash = namespace.add_setting(
|
|
||||||
global_name='APPEND_SLASH', default=settings.APPEND_SLASH,
|
|
||||||
help_text=_(
|
|
||||||
'When set to True, if the request URL does not match any of the '
|
|
||||||
'patterns in the URLconf and it doesn\'t end in a slash, an HTTP '
|
|
||||||
'redirect is issued to the same URL with a slash appended. Note '
|
|
||||||
'that the redirect may cause any data submitted in a POST request '
|
|
||||||
'to be lost. The APPEND_SLASH setting is only used if '
|
|
||||||
'CommonMiddleware is installed (see Middleware). See also '
|
|
||||||
'PREPEND_WWW.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setting_django_auth_password_validators = namespace.add_setting(
|
|
||||||
global_name='AUTH_PASSWORD_VALIDATORS',
|
|
||||||
default=settings.AUTH_PASSWORD_VALIDATORS,
|
|
||||||
help_text=_(
|
|
||||||
'The list of validators that are used to check the strength of '
|
|
||||||
'user\'s passwords.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setting_django_databases = namespace.add_setting(
|
|
||||||
global_name='DATABASES', default=settings.DATABASES,
|
|
||||||
help_text=_(
|
|
||||||
'A dictionary containing the settings for all databases to be used '
|
|
||||||
'with Django. It is a nested dictionary whose contents map a '
|
|
||||||
'database alias to a dictionary containing the options for an '
|
|
||||||
'individual database. The DATABASES setting must configure a '
|
|
||||||
'default database; any number of additional databases may also '
|
|
||||||
'be specified.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_data_upload_max_memory_size = namespace.add_setting(
|
|
||||||
global_name='DATA_UPLOAD_MAX_MEMORY_SIZE',
|
|
||||||
default=settings.DATA_UPLOAD_MAX_MEMORY_SIZE,
|
|
||||||
help_text=_(
|
|
||||||
'Default: 2621440 (i.e. 2.5 MB). The maximum size in bytes that a '
|
|
||||||
'request body may be before a SuspiciousOperation '
|
|
||||||
'(RequestDataTooBig) is raised. The check is done when accessing '
|
|
||||||
'request.body or request.POST and is calculated against the total '
|
|
||||||
'request size excluding any file upload data. You can set this to '
|
|
||||||
'None to disable the check. Applications that are expected to '
|
|
||||||
'receive unusually large form posts should tune this setting. The '
|
|
||||||
'amount of request data is correlated to the amount of memory '
|
|
||||||
'needed to process the request and populate the GET and POST '
|
|
||||||
'dictionaries. Large requests could be used as a '
|
|
||||||
'denial-of-service attack vector if left unchecked. Since web '
|
|
||||||
'servers don\'t typically perform deep request inspection, it\'s '
|
|
||||||
'not possible to perform a similar check at that level. See also '
|
|
||||||
'FILE_UPLOAD_MAX_MEMORY_SIZE.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_default_from_email = namespace.add_setting(
|
|
||||||
global_name='DEFAULT_FROM_EMAIL',
|
|
||||||
default=settings.DEFAULT_FROM_EMAIL,
|
|
||||||
help_text=_(
|
|
||||||
'Default: \'webmaster@localhost\' '
|
|
||||||
'Default email address to use for various automated correspondence '
|
|
||||||
'from the site manager(s). This doesn\'t include error messages sent '
|
|
||||||
'to ADMINS and MANAGERS; for that, see SERVER_EMAIL.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_disallowed_user_agents = namespace.add_setting(
|
|
||||||
global_name='DISALLOWED_USER_AGENTS',
|
|
||||||
default=settings.DISALLOWED_USER_AGENTS,
|
|
||||||
help_text=_(
|
|
||||||
'Default: [] (Empty list). List of compiled regular expression '
|
|
||||||
'objects representing User-Agent strings that are not allowed to '
|
|
||||||
'visit any page, systemwide. Use this for bad robots/crawlers. '
|
|
||||||
'This is only used if CommonMiddleware is installed '
|
|
||||||
'(see Middleware).'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_email_backend = namespace.add_setting(
|
|
||||||
global_name='EMAIL_BACKEND',
|
|
||||||
default=settings.EMAIL_BACKEND,
|
|
||||||
help_text=_(
|
|
||||||
'Default: \'django.core.mail.backends.smtp.EmailBackend\'. The '
|
|
||||||
'backend to use for sending emails.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_email_host = namespace.add_setting(
|
|
||||||
global_name='EMAIL_HOST',
|
|
||||||
default=settings.EMAIL_HOST,
|
|
||||||
help_text=_(
|
|
||||||
'Default: \'localhost\'. The host to use for sending email.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_email_host_password = namespace.add_setting(
|
|
||||||
global_name='EMAIL_HOST_PASSWORD',
|
|
||||||
default=settings.EMAIL_HOST_PASSWORD,
|
|
||||||
help_text=_(
|
|
||||||
'Default: \'\' (Empty string). Password to use for the SMTP '
|
|
||||||
'server defined in EMAIL_HOST. This setting is used in '
|
|
||||||
'conjunction with EMAIL_HOST_USER when authenticating to the '
|
|
||||||
'SMTP server. If either of these settings is empty, '
|
|
||||||
'Django won\'t attempt authentication.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_email_host_user = namespace.add_setting(
|
|
||||||
global_name='EMAIL_HOST_USER',
|
|
||||||
default=settings.EMAIL_HOST_USER,
|
|
||||||
help_text=_(
|
|
||||||
'Default: \'\' (Empty string). Username to use for the SMTP '
|
|
||||||
'server defined in EMAIL_HOST. If empty, Django won\'t attempt '
|
|
||||||
'authentication.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_email_port = namespace.add_setting(
|
|
||||||
global_name='EMAIL_PORT',
|
|
||||||
default=settings.EMAIL_PORT,
|
|
||||||
help_text=_(
|
|
||||||
'Default: 25. Port to use for the SMTP server defined in EMAIL_HOST.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_email_timeout = namespace.add_setting(
|
|
||||||
global_name='EMAIL_TIMEOUT',
|
|
||||||
default=settings.EMAIL_TIMEOUT,
|
|
||||||
help_text=_(
|
|
||||||
'Default: None. Specifies a timeout in seconds for blocking '
|
|
||||||
'operations like the connection attempt.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_email_user_tls = namespace.add_setting(
|
|
||||||
global_name='EMAIL_USE_TLS',
|
|
||||||
default=settings.EMAIL_USE_TLS,
|
|
||||||
help_text=_(
|
|
||||||
'Default: False. Whether to use a TLS (secure) connection when '
|
|
||||||
'talking to the SMTP server. This is used for explicit TLS '
|
|
||||||
'connections, generally on port 587. If you are experiencing '
|
|
||||||
'hanging connections, see the implicit TLS setting EMAIL_USE_SSL.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_email_user_ssl = namespace.add_setting(
|
|
||||||
global_name='EMAIL_USE_SSL',
|
|
||||||
default=settings.EMAIL_USE_SSL,
|
|
||||||
help_text=_(
|
|
||||||
'Default: False. Whether to use an implicit TLS (secure) connection '
|
|
||||||
'when talking to the SMTP server. In most email documentation this '
|
|
||||||
'type of TLS connection is referred to as SSL. It is generally used '
|
|
||||||
'on port 465. If you are experiencing problems, see the explicit '
|
|
||||||
'TLS setting EMAIL_USE_TLS. Note that EMAIL_USE_TLS/EMAIL_USE_SSL '
|
|
||||||
'are mutually exclusive, so only set one of those settings to True.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_file_upload_max_memory_size = namespace.add_setting(
|
|
||||||
global_name='FILE_UPLOAD_MAX_MEMORY_SIZE',
|
|
||||||
default=settings.FILE_UPLOAD_MAX_MEMORY_SIZE,
|
|
||||||
help_text=_(
|
|
||||||
'Default: 2621440 (i.e. 2.5 MB). The maximum size (in bytes) '
|
|
||||||
'that an upload will be before it gets streamed to the file '
|
|
||||||
'system. See Managing files for details. See also '
|
|
||||||
'DATA_UPLOAD_MAX_MEMORY_SIZE.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_login_url = namespace.add_setting(
|
|
||||||
global_name='LOGIN_URL',
|
|
||||||
default=settings.LOGIN_URL,
|
|
||||||
help_text=_(
|
|
||||||
'Default: \'/accounts/login/\' The URL where requests are '
|
|
||||||
'redirected for login, especially when using the login_required() '
|
|
||||||
'decorator. This setting also accepts named URL patterns which '
|
|
||||||
'can be used to reduce configuration duplication since you '
|
|
||||||
'don\'t have to define the URL in two places (settings '
|
|
||||||
'and URLconf).'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setting_django_login_redirect_url = namespace.add_setting(
|
|
||||||
global_name='LOGIN_REDIRECT_URL',
|
|
||||||
default=settings.LOGIN_REDIRECT_URL,
|
|
||||||
help_text=_(
|
|
||||||
'Default: \'/accounts/profile/\' The URL where requests are '
|
|
||||||
'redirected after login when the contrib.auth.login view gets no '
|
|
||||||
'next parameter. This is used by the login_required() decorator, '
|
|
||||||
'for example. This setting also accepts named URL patterns which '
|
|
||||||
'can be used to reduce configuration duplication since you don\'t '
|
|
||||||
'have to define the URL in two places (settings and URLconf).'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_logout_redirect_url = namespace.add_setting(
|
|
||||||
global_name='LOGOUT_REDIRECT_URL',
|
|
||||||
default=settings.LOGOUT_REDIRECT_URL,
|
|
||||||
help_text=_(
|
|
||||||
'Default: None. The URL where requests are redirected after a user '
|
|
||||||
'logs out using LogoutView (if the view doesn\'t get a next_page '
|
|
||||||
'argument). If None, no redirect will be performed and the logout '
|
|
||||||
'view will be rendered. This setting also accepts named URL '
|
|
||||||
'patterns which can be used to reduce configuration duplication '
|
|
||||||
'since you don\'t have to define the URL in two places (settings '
|
|
||||||
'and URLconf).'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setting_django_internal_ips = namespace.add_setting(
|
|
||||||
global_name='INTERNAL_IPS',
|
|
||||||
default=settings.INTERNAL_IPS,
|
|
||||||
help_text=_(
|
|
||||||
'A list of IP addresses, as strings, that: Allow the debug() '
|
|
||||||
'context processor to add some variables to the template context. '
|
|
||||||
'Can use the admindocs bookmarklets even if not logged in as a '
|
|
||||||
'staff user. Are marked as "internal" (as opposed to "EXTERNAL") '
|
|
||||||
'in AdminEmailHandler emails.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_languages = namespace.add_setting(
|
|
||||||
global_name='LANGUAGES',
|
|
||||||
default=settings.LANGUAGES,
|
|
||||||
help_text=_(
|
|
||||||
'A list of all available languages. The list is a list of '
|
|
||||||
'two-tuples in the format (language code, language name) '
|
|
||||||
'for example, (\'ja\', \'Japanese\'). This specifies which '
|
|
||||||
'languages are available for language selection. '
|
|
||||||
'Generally, the default value should suffice. Only set this '
|
|
||||||
'setting if you want to restrict language selection to a '
|
|
||||||
'subset of the Django-provided languages. '
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_language_code = namespace.add_setting(
|
|
||||||
global_name='LANGUAGE_CODE',
|
|
||||||
default=settings.LANGUAGE_CODE,
|
|
||||||
help_text=_(
|
|
||||||
'A string representing the language code for this installation. '
|
|
||||||
'This should be in standard language ID format. For example, U.S. '
|
|
||||||
'English is "en-us". It serves two purposes: If the locale '
|
|
||||||
'middleware isn\'t in use, it decides which translation is served '
|
|
||||||
'to all users. If the locale middleware is active, it provides a '
|
|
||||||
'fallback language in case the user\'s preferred language can\'t '
|
|
||||||
'be determined or is not supported by the website. It also provides '
|
|
||||||
'the fallback translation when a translation for a given literal '
|
|
||||||
'doesn\'t exist for the user\'s preferred language.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_static_url = namespace.add_setting(
|
|
||||||
global_name='STATIC_URL',
|
|
||||||
default=settings.STATIC_URL,
|
|
||||||
help_text=_(
|
|
||||||
'URL to use when referring to static files located in STATIC_ROOT. '
|
|
||||||
'Example: "/static/" or "http://static.example.com/" '
|
|
||||||
'If not None, this will be used as the base path for asset '
|
|
||||||
'definitions (the Media class) and the staticfiles app. '
|
|
||||||
'It must end in a slash if set to a non-empty value.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_staticfiles_storage = namespace.add_setting(
|
|
||||||
global_name='STATICFILES_STORAGE',
|
|
||||||
default=settings.STATICFILES_STORAGE,
|
|
||||||
help_text=_(
|
|
||||||
'The file storage engine to use when collecting static files with '
|
|
||||||
'the collectstatic management command. A ready-to-use instance of '
|
|
||||||
'the storage backend defined in this setting can be found at '
|
|
||||||
'django.contrib.staticfiles.storage.staticfiles_storage.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_time_zone = namespace.add_setting(
|
|
||||||
global_name='TIME_ZONE',
|
|
||||||
default=settings.TIME_ZONE,
|
|
||||||
help_text=_(
|
|
||||||
'A string representing the time zone for this installation. '
|
|
||||||
'Note that this isn\'t necessarily the time zone of the server. '
|
|
||||||
'For example, one server may serve multiple Django-powered sites, '
|
|
||||||
'each with a separate time zone setting.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_django_wsgi_application = namespace.add_setting(
|
|
||||||
global_name='WSGI_APPLICATION',
|
|
||||||
default=settings.WSGI_APPLICATION,
|
|
||||||
help_text=_(
|
|
||||||
'The full Python path of the WSGI application object that Django\'s '
|
|
||||||
'built-in servers (e.g. runserver) will use. The django-admin '
|
|
||||||
'startproject management command will create a simple wsgi.py '
|
|
||||||
'file with an application callable in it, and point this setting '
|
|
||||||
'to that application.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
namespace = Namespace(label=_('Celery'), name='celery')
|
|
||||||
|
|
||||||
setting_celery_broker_url = namespace.add_setting(
|
|
||||||
global_name='BROKER_URL', default=settings.BROKER_URL,
|
|
||||||
help_text=_(
|
|
||||||
'Default: "amqp://". Default broker URL. This must be a URL in '
|
|
||||||
'the form of: transport://userid:password@hostname:port/virtual_host '
|
|
||||||
'Only the scheme part (transport://) is required, the rest is '
|
|
||||||
'optional, and defaults to the specific transports default values.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
setting_celery_result_backend = namespace.add_setting(
|
|
||||||
global_name='CELERY_RESULT_BACKEND',
|
|
||||||
default=settings.CELERY_RESULT_BACKEND,
|
|
||||||
help_text=_(
|
|
||||||
'Default: No result backend enabled by default. The backend used '
|
|
||||||
'to store task results (tombstones). Refer to '
|
|
||||||
'http://docs.celeryproject.org/en/v4.1.0/userguide/configuration.'
|
|
||||||
'html#result-backend'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,23 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import yaml
|
from mayan.apps.storage.utils import get_storage_subclass
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django.utils.module_loading import import_string
|
|
||||||
|
|
||||||
from .settings import (
|
from .settings import (
|
||||||
setting_shared_storage, setting_shared_storage_arguments
|
setting_shared_storage, setting_shared_storage_arguments
|
||||||
)
|
)
|
||||||
|
|
||||||
storage_sharedupload = import_string(
|
storage_sharedupload = get_storage_subclass(
|
||||||
dotted_path=setting_shared_storage.value
|
dotted_path=setting_shared_storage.value
|
||||||
)(
|
)(**setting_shared_storage_arguments.value)
|
||||||
**yaml.load(
|
|
||||||
stream=setting_shared_storage_arguments.value or '{}',
|
|
||||||
Loader=SafeLoader
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from django_downloadview import assert_download_response
|
|||||||
from mayan.apps.acls.tests.mixins import ACLTestCaseMixin
|
from mayan.apps.acls.tests.mixins import ACLTestCaseMixin
|
||||||
from mayan.apps.permissions.classes import Permission
|
from mayan.apps.permissions.classes import Permission
|
||||||
from mayan.apps.smart_settings.classes import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
from mayan.apps.user_management.tests.mixins import UserTestMixin
|
||||||
|
|
||||||
from .mixins import (
|
from .mixins import (
|
||||||
ClientMethodsTestCaseMixin, ConnectionsCheckTestCaseMixin,
|
ClientMethodsTestCaseMixin, ConnectionsCheckTestCaseMixin,
|
||||||
@@ -21,7 +22,7 @@ class BaseTestCase(
|
|||||||
SilenceLoggerTestCaseMixin, ConnectionsCheckTestCaseMixin,
|
SilenceLoggerTestCaseMixin, ConnectionsCheckTestCaseMixin,
|
||||||
RandomPrimaryKeyModelMonkeyPatchMixin, ACLTestCaseMixin,
|
RandomPrimaryKeyModelMonkeyPatchMixin, ACLTestCaseMixin,
|
||||||
ModelTestCaseMixin, OpenFileCheckTestCaseMixin,
|
ModelTestCaseMixin, OpenFileCheckTestCaseMixin,
|
||||||
TempfileCheckTestCasekMixin, TestCase
|
TempfileCheckTestCasekMixin, UserTestMixin, TestCase
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
This is the most basic test case class any test in the project should use.
|
This is the most basic test case class any test in the project should use.
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
|
|
||||||
class NullFile(object):
|
class NullFile(object):
|
||||||
def write(self, string):
|
def write(self, string):
|
||||||
@@ -13,3 +17,9 @@ def mute_stdout():
|
|||||||
sys.stdout = NullFile()
|
sys.stdout = NullFile()
|
||||||
yield
|
yield
|
||||||
sys.stdout = stdout_old
|
sys.stdout = stdout_old
|
||||||
|
|
||||||
|
|
||||||
|
def as_id_list(items):
|
||||||
|
return ','.join(
|
||||||
|
[force_text(item.pk) for item in items]
|
||||||
|
)
|
||||||
|
|||||||
@@ -21,14 +21,6 @@ def check_for_sqlite():
|
|||||||
return settings.DATABASES['default']['ENGINE'] == DJANGO_SQLITE_BACKEND and settings.DEBUG is False
|
return settings.DATABASES['default']['ENGINE'] == DJANGO_SQLITE_BACKEND and settings.DEBUG is False
|
||||||
|
|
||||||
|
|
||||||
def encapsulate(function):
|
|
||||||
# Workaround Django ticket 15791
|
|
||||||
# Changeset 16045
|
|
||||||
# http://stackoverflow.com/questions/6861601/
|
|
||||||
# cannot-resolve-callable-context-variable/6955045#6955045
|
|
||||||
return lambda: function
|
|
||||||
|
|
||||||
|
|
||||||
def get_related_field(model, related_field_name):
|
def get_related_field(model, related_field_name):
|
||||||
try:
|
try:
|
||||||
local_field_name, remaining_field_path = related_field_name.split(
|
local_field_name, remaining_field_path = related_field_name.split(
|
||||||
|
|||||||
@@ -7,11 +7,6 @@ import shutil
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
import PyPDF2
|
import PyPDF2
|
||||||
import sh
|
import sh
|
||||||
import yaml
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@@ -20,16 +15,14 @@ from mayan.apps.storage.utils import NamedTemporaryFile
|
|||||||
|
|
||||||
from ..classes import ConverterBase
|
from ..classes import ConverterBase
|
||||||
from ..exceptions import PageCountError
|
from ..exceptions import PageCountError
|
||||||
from ..settings import setting_graphics_backend_config
|
from ..settings import setting_graphics_backend_arguments
|
||||||
|
|
||||||
from ..literals import (
|
from ..literals import (
|
||||||
DEFAULT_PDFTOPPM_DPI, DEFAULT_PDFTOPPM_FORMAT, DEFAULT_PDFTOPPM_PATH,
|
DEFAULT_PDFTOPPM_DPI, DEFAULT_PDFTOPPM_FORMAT, DEFAULT_PDFTOPPM_PATH,
|
||||||
DEFAULT_PDFINFO_PATH
|
DEFAULT_PDFINFO_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
pdftoppm_path = yaml.load(
|
pdftoppm_path = setting_graphics_backend_arguments.value.get(
|
||||||
stream=setting_graphics_backend_config.value, Loader=SafeLoader
|
|
||||||
).get(
|
|
||||||
'pdftoppm_path', DEFAULT_PDFTOPPM_PATH
|
'pdftoppm_path', DEFAULT_PDFTOPPM_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,26 +32,20 @@ except sh.CommandNotFound:
|
|||||||
pdftoppm = None
|
pdftoppm = None
|
||||||
else:
|
else:
|
||||||
pdftoppm_format = '-{}'.format(
|
pdftoppm_format = '-{}'.format(
|
||||||
yaml.load(
|
setting_graphics_backend_arguments.value.get(
|
||||||
stream=setting_graphics_backend_config.value, Loader=SafeLoader
|
|
||||||
).get(
|
|
||||||
'pdftoppm_format', DEFAULT_PDFTOPPM_FORMAT
|
'pdftoppm_format', DEFAULT_PDFTOPPM_FORMAT
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
pdftoppm_dpi = format(
|
pdftoppm_dpi = format(
|
||||||
yaml.load(
|
setting_graphics_backend_arguments.value.get(
|
||||||
stream=setting_graphics_backend_config.value, Loader=SafeLoader
|
|
||||||
).get(
|
|
||||||
'pdftoppm_dpi', DEFAULT_PDFTOPPM_DPI
|
'pdftoppm_dpi', DEFAULT_PDFTOPPM_DPI
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
pdftoppm = pdftoppm.bake(pdftoppm_format, '-r', pdftoppm_dpi)
|
pdftoppm = pdftoppm.bake(pdftoppm_format, '-r', pdftoppm_dpi)
|
||||||
|
|
||||||
pdfinfo_path = yaml.load(
|
pdfinfo_path = setting_graphics_backend_arguments.value.get(
|
||||||
stream=setting_graphics_backend_config.value, Loader=SafeLoader
|
|
||||||
).get(
|
|
||||||
'pdfinfo_path', DEFAULT_PDFINFO_PATH
|
'pdfinfo_path', DEFAULT_PDFINFO_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,10 @@ import shutil
|
|||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import sh
|
import sh
|
||||||
import yaml
|
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.common.serialization import yaml_load
|
||||||
from mayan.apps.mimetype.api import get_mimetype
|
from mayan.apps.mimetype.api import get_mimetype
|
||||||
from mayan.apps.storage.settings import setting_temporary_directory
|
from mayan.apps.storage.settings import setting_temporary_directory
|
||||||
from mayan.apps.storage.utils import (
|
from mayan.apps.storage.utils import (
|
||||||
@@ -27,16 +22,14 @@ from .literals import (
|
|||||||
CONVERTER_OFFICE_FILE_MIMETYPES, DEFAULT_LIBREOFFICE_PATH,
|
CONVERTER_OFFICE_FILE_MIMETYPES, DEFAULT_LIBREOFFICE_PATH,
|
||||||
DEFAULT_PAGE_NUMBER, DEFAULT_PILLOW_FORMAT
|
DEFAULT_PAGE_NUMBER, DEFAULT_PILLOW_FORMAT
|
||||||
)
|
)
|
||||||
from .settings import setting_graphics_backend_config
|
from .settings import setting_graphics_backend_arguments
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
libreoffice_path = setting_graphics_backend_arguments.value.get(
|
||||||
BACKEND_CONFIG = yaml.load(
|
|
||||||
stream=setting_graphics_backend_config.value, Loader=SafeLoader
|
|
||||||
)
|
|
||||||
libreoffice_path = BACKEND_CONFIG.get(
|
|
||||||
'libreoffice_path', DEFAULT_LIBREOFFICE_PATH
|
'libreoffice_path', DEFAULT_LIBREOFFICE_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ConverterBase(object):
|
class ConverterBase(object):
|
||||||
def __init__(self, file_object, mime_type=None):
|
def __init__(self, file_object, mime_type=None):
|
||||||
@@ -62,9 +55,7 @@ class ConverterBase(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def get_page(self, output_format=None):
|
def get_page(self, output_format=None):
|
||||||
output_format = output_format or yaml.load(
|
output_format = output_format or setting_graphics_backend_arguments.value.get(
|
||||||
stream=setting_graphics_backend_config.value, Loader=SafeLoader
|
|
||||||
).get(
|
|
||||||
'pillow_format', DEFAULT_PILLOW_FORMAT
|
'pillow_format', DEFAULT_PILLOW_FORMAT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.common.serialization import yaml_load
|
||||||
|
|
||||||
from .models import Transformation
|
from .models import Transformation
|
||||||
|
|
||||||
|
|
||||||
@@ -21,7 +18,7 @@ class TransformationForm(forms.ModelForm):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
try:
|
try:
|
||||||
yaml.load(stream=self.cleaned_data['arguments'], Loader=SafeLoader)
|
yaml_load(stream=self.cleaned_data['arguments'])
|
||||||
except yaml.YAMLError:
|
except yaml.YAMLError:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_(
|
_(
|
||||||
|
|||||||
@@ -2,16 +2,11 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader, CSafeDumper as SafeDumper
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader, SafeDumper
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
|
|
||||||
|
from mayan.apps.common.serialization import yaml_dump, yaml_load
|
||||||
|
|
||||||
from .transformations import BaseTransformation
|
from .transformations import BaseTransformation
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -23,8 +18,8 @@ class TransformationManager(models.Manager):
|
|||||||
|
|
||||||
self.create(
|
self.create(
|
||||||
content_type=content_type, object_id=obj.pk,
|
content_type=content_type, object_id=obj.pk,
|
||||||
name=transformation.name, arguments=yaml.dump(
|
name=transformation.name, arguments=yaml_dump(
|
||||||
data=arguments, Dumper=SafeDumper
|
data=arguments
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -96,9 +91,8 @@ class TransformationManager(models.Manager):
|
|||||||
# Some transformations don't require arguments
|
# Some transformations don't require arguments
|
||||||
# return an empty dictionary as ** doesn't allow None
|
# return an empty dictionary as ** doesn't allow None
|
||||||
if transformation.arguments:
|
if transformation.arguments:
|
||||||
kwargs = yaml.load(
|
kwargs = yaml_load(
|
||||||
stream=transformation.arguments,
|
stream=transformation.arguments,
|
||||||
Loader=SafeLoader
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
from .literals import (
|
from .literals import (
|
||||||
DEFAULT_LIBREOFFICE_PATH, DEFAULT_PDFTOPPM_DPI, DEFAULT_PDFTOPPM_FORMAT,
|
DEFAULT_LIBREOFFICE_PATH, DEFAULT_PDFTOPPM_DPI, DEFAULT_PDFTOPPM_FORMAT,
|
||||||
@@ -16,22 +16,15 @@ setting_graphics_backend = namespace.add_setting(
|
|||||||
help_text=_('Graphics conversion backend to use.'),
|
help_text=_('Graphics conversion backend to use.'),
|
||||||
global_name='CONVERTER_GRAPHICS_BACKEND',
|
global_name='CONVERTER_GRAPHICS_BACKEND',
|
||||||
)
|
)
|
||||||
setting_graphics_backend_config = namespace.add_setting(
|
setting_graphics_backend_arguments = namespace.add_setting(
|
||||||
default='''
|
default={
|
||||||
{{
|
'libreoffice_path': DEFAULT_LIBREOFFICE_PATH,
|
||||||
libreoffice_path: {},
|
'pdftoppm_dpi': DEFAULT_PDFTOPPM_DPI,
|
||||||
pdftoppm_dpi: {},
|
'pdftoppm_format': DEFAULT_PDFTOPPM_FORMAT,
|
||||||
pdftoppm_format: {},
|
'pdftoppm_path': DEFAULT_PDFTOPPM_PATH,
|
||||||
pdftoppm_path: {},
|
'pdfinfo_path': DEFAULT_PDFINFO_PATH,
|
||||||
pdfinfo_path: {},
|
'pillow_format': DEFAULT_PILLOW_FORMAT,
|
||||||
pillow_format: {}
|
}, help_text=_(
|
||||||
|
|
||||||
}}
|
|
||||||
'''.replace('\n', '').format(
|
|
||||||
DEFAULT_LIBREOFFICE_PATH, DEFAULT_PDFTOPPM_DPI,
|
|
||||||
DEFAULT_PDFTOPPM_FORMAT, DEFAULT_PDFTOPPM_PATH, DEFAULT_PDFINFO_PATH,
|
|
||||||
DEFAULT_PILLOW_FORMAT
|
|
||||||
), help_text=_(
|
|
||||||
'Configuration options for the graphics conversion backend.'
|
'Configuration options for the graphics conversion backend.'
|
||||||
), global_name='CONVERTER_GRAPHICS_BACKEND_CONFIG', quoted=True
|
), global_name='CONVERTER_GRAPHICS_BACKEND_ARGUMENTS'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.deconstruct import deconstructible
|
from django.utils.deconstruct import deconstructible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.common.serialization import yaml_load
|
||||||
|
|
||||||
|
|
||||||
@deconstructible
|
@deconstructible
|
||||||
class YAMLValidator(object):
|
class YAMLValidator(object):
|
||||||
@@ -20,7 +17,7 @@ class YAMLValidator(object):
|
|||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
try:
|
try:
|
||||||
yaml.load(stream=value, Loader=SafeLoader)
|
yaml_load(stream=value)
|
||||||
except yaml.error.YAMLError:
|
except yaml.error.YAMLError:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_('Enter a valid YAML value.'),
|
_('Enter a valid YAML value.'),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import os
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
namespace = Namespace(label=_('Signatures'), name='django_gpg')
|
namespace = Namespace(label=_('Signatures'), name='django_gpg')
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ from .literals import (
|
|||||||
from .mixins import IndexTestMixin, IndexViewTestMixin
|
from .mixins import IndexTestMixin, IndexViewTestMixin
|
||||||
|
|
||||||
|
|
||||||
class IndexViewTestCase(IndexTestMixin, IndexViewTestMixin, GenericDocumentViewTestCase):
|
class IndexViewTestCase(
|
||||||
|
IndexTestMixin, IndexViewTestMixin, GenericDocumentViewTestCase
|
||||||
|
):
|
||||||
|
auto_upload_document = False
|
||||||
|
|
||||||
def test_index_create_view_no_permission(self):
|
def test_index_create_view_no_permission(self):
|
||||||
response = self._request_test_index_create_view()
|
response = self._request_test_index_create_view()
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
@@ -75,6 +79,45 @@ class IndexViewTestCase(IndexTestMixin, IndexViewTestMixin, GenericDocumentViewT
|
|||||||
self.test_index.refresh_from_db()
|
self.test_index.refresh_from_db()
|
||||||
self.assertEqual(self.test_index.label, TEST_INDEX_LABEL_EDITED)
|
self.assertEqual(self.test_index.label, TEST_INDEX_LABEL_EDITED)
|
||||||
|
|
||||||
|
def test_index_rebuild_view_no_permission(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_index()
|
||||||
|
|
||||||
|
self.test_index.node_templates.create(
|
||||||
|
parent=self.test_index.template_root,
|
||||||
|
expression=TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION,
|
||||||
|
link_documents=True
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_index_rebuild_view()
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
self.assertEqual(IndexInstanceNode.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_index_rebuild_view_with_access(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_index()
|
||||||
|
|
||||||
|
self.test_index.node_templates.create(
|
||||||
|
parent=self.test_index.template_root,
|
||||||
|
expression=TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION,
|
||||||
|
link_documents=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_index,
|
||||||
|
permission=permission_document_indexing_rebuild
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_index_rebuild_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertNotEqual(IndexInstanceNode.objects.count(), 0)
|
||||||
|
|
||||||
|
|
||||||
|
class IndexInstanceViewTestCase(
|
||||||
|
IndexTestMixin, IndexViewTestMixin, GenericDocumentViewTestCase
|
||||||
|
):
|
||||||
def _request_index_instance_node_view(self, index_instance_node):
|
def _request_index_instance_node_view(self, index_instance_node):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='indexing:index_instance_node_view', kwargs={
|
viewname='indexing:index_instance_node_view', kwargs={
|
||||||
@@ -103,9 +146,13 @@ class IndexViewTestCase(IndexTestMixin, IndexViewTestMixin, GenericDocumentViewT
|
|||||||
)
|
)
|
||||||
self.assertContains(response, text=TEST_INDEX_LABEL, status_code=200)
|
self.assertContains(response, text=TEST_INDEX_LABEL, status_code=200)
|
||||||
|
|
||||||
|
|
||||||
|
class IndexToolsViewTestCase(
|
||||||
|
IndexTestMixin, IndexViewTestMixin, GenericDocumentViewTestCase
|
||||||
|
):
|
||||||
def _request_indexes_rebuild_get_view(self):
|
def _request_indexes_rebuild_get_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='indexing:rebuild_index_instances',
|
viewname='indexing:rebuild_index_instances'
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_indexes_rebuild_post_view(self):
|
def _request_indexes_rebuild_post_view(self):
|
||||||
@@ -149,36 +196,3 @@ class IndexViewTestCase(IndexTestMixin, IndexViewTestMixin, GenericDocumentViewT
|
|||||||
|
|
||||||
# An instance root exists
|
# An instance root exists
|
||||||
self.assertTrue(self.test_index.instance_root.pk)
|
self.assertTrue(self.test_index.instance_root.pk)
|
||||||
|
|
||||||
def test_index_rebuild_view_no_permission(self):
|
|
||||||
self._create_test_index()
|
|
||||||
|
|
||||||
self.test_index.node_templates.create(
|
|
||||||
parent=self.test_index.template_root,
|
|
||||||
expression=TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION,
|
|
||||||
link_documents=True
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self._request_test_index_rebuild_view()
|
|
||||||
self.assertEqual(response.status_code, 404)
|
|
||||||
|
|
||||||
self.assertEqual(IndexInstanceNode.objects.count(), 0)
|
|
||||||
|
|
||||||
def test_index_rebuild_view_with_access(self):
|
|
||||||
self._create_test_index()
|
|
||||||
|
|
||||||
self.test_index.node_templates.create(
|
|
||||||
parent=self.test_index.template_root,
|
|
||||||
expression=TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION,
|
|
||||||
link_documents=True
|
|
||||||
)
|
|
||||||
|
|
||||||
self.grant_access(
|
|
||||||
obj=self.test_index,
|
|
||||||
permission=permission_document_indexing_rebuild
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self._request_test_index_rebuild_view()
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
|
|
||||||
self.assertNotEqual(IndexInstanceNode.objects.count(), 0)
|
|
||||||
|
|||||||
@@ -308,7 +308,6 @@ class IndexListView(SingleObjectListView):
|
|||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
'hide_object': True,
|
|
||||||
'hide_links': True,
|
'hide_links': True,
|
||||||
'hide_object': True,
|
'hide_object': True,
|
||||||
'no_results_icon': icon_index,
|
'no_results_icon': icon_index,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
namespace = Namespace(label=_('Document parsing'), name='document_parsing')
|
namespace = Namespace(label=_('Document parsing'), name='document_parsing')
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.22 on 2019-07-11 05:44
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import mayan.apps.document_signatures.models
|
||||||
|
import mayan.apps.storage.classes
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('document_signatures', '0008_auto_20180429_0759'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='detachedsignature',
|
||||||
|
name='signature_file',
|
||||||
|
field=models.FileField(blank=True, null=True, storage=mayan.apps.storage.classes.FakeStorageSubclass(), upload_to=mayan.apps.document_signatures.models.upload_to, verbose_name='Signature file'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -5,7 +5,7 @@ import os
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
namespace = Namespace(label=_('Document signatures'), name='signatures')
|
namespace = Namespace(label=_('Document signatures'), name='signatures')
|
||||||
|
|
||||||
@@ -18,9 +18,9 @@ setting_storage_backend = namespace.add_setting(
|
|||||||
)
|
)
|
||||||
setting_storage_backend_arguments = namespace.add_setting(
|
setting_storage_backend_arguments = namespace.add_setting(
|
||||||
global_name='SIGNATURES_STORAGE_BACKEND_ARGUMENTS',
|
global_name='SIGNATURES_STORAGE_BACKEND_ARGUMENTS',
|
||||||
default='{{location: {}}}'.format(
|
default={
|
||||||
os.path.join(settings.MEDIA_ROOT, 'document_signatures')
|
'location': os.path.join(settings.MEDIA_ROOT, 'document_signatures')
|
||||||
), quoted=True, help_text=_(
|
}, help_text=_(
|
||||||
'Arguments to pass to the SIGNATURE_STORAGE_BACKEND. '
|
'Arguments to pass to the SIGNATURE_STORAGE_BACKEND. '
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,23 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import yaml
|
from mayan.apps.storage.utils import get_storage_subclass
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django.utils.module_loading import import_string
|
|
||||||
|
|
||||||
from .settings import (
|
from .settings import (
|
||||||
setting_storage_backend, setting_storage_backend_arguments
|
setting_storage_backend, setting_storage_backend_arguments
|
||||||
)
|
)
|
||||||
|
|
||||||
storage_detachedsignature = import_string(
|
storage_detachedsignature = get_storage_subclass(
|
||||||
dotted_path=setting_storage_backend.value
|
dotted_path=setting_storage_backend.value
|
||||||
)(
|
)(**setting_storage_backend_arguments.value)
|
||||||
**yaml.load(
|
|
||||||
stream=setting_storage_backend_arguments.value or '{}',
|
|
||||||
Loader=SafeLoader
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from .storages import storage_workflowimagecache
|
|||||||
from .tasks import task_generate_workflow_image
|
from .tasks import task_generate_workflow_image
|
||||||
|
|
||||||
|
|
||||||
class APIDocumentTypeWorkflowListView(generics.ListAPIView):
|
class APIDocumentTypeWorkflowRuntimeProxyListView(generics.ListAPIView):
|
||||||
"""
|
"""
|
||||||
get: Returns a list of all the document type workflows.
|
get: Returns a list of all the document type workflows.
|
||||||
"""
|
"""
|
||||||
@@ -214,7 +214,7 @@ class APIWorkflowImageView(generics.RetrieveAPIView):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class APIWorkflowListView(generics.ListCreateAPIView):
|
class APIWorkflowRuntimeProxyListView(generics.ListCreateAPIView):
|
||||||
"""
|
"""
|
||||||
get: Returns a list of all the workflows.
|
get: Returns a list of all the workflows.
|
||||||
post: Create a new workflow.
|
post: Create a new workflow.
|
||||||
@@ -229,7 +229,7 @@ class APIWorkflowListView(generics.ListCreateAPIView):
|
|||||||
if not self.request:
|
if not self.request:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return super(APIWorkflowListView, self).get_serializer(*args, **kwargs)
|
return super(APIWorkflowRuntimeProxyListView, self).get_serializer(*args, **kwargs)
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
if self.request.method == 'GET':
|
if self.request.method == 'GET':
|
||||||
|
|||||||
@@ -29,27 +29,27 @@ from .handlers import (
|
|||||||
)
|
)
|
||||||
from .html_widgets import WorkflowLogExtraDataWidget, widget_transition_events
|
from .html_widgets import WorkflowLogExtraDataWidget, widget_transition_events
|
||||||
from .links import (
|
from .links import (
|
||||||
link_document_workflow_instance_list, link_setup_document_type_workflows,
|
link_workflow_instance_list, link_document_type_workflow_templates,
|
||||||
link_setup_workflow_document_types, link_setup_workflow_create,
|
link_workflow_template_document_types, link_workflow_template_create,
|
||||||
link_setup_workflow_delete, link_setup_workflow_edit,
|
link_workflow_template_delete, link_workflow_template_edit,
|
||||||
link_setup_workflow_list, link_setup_workflow_states,
|
link_workflow_template_list, link_workflow_template_state_list,
|
||||||
link_setup_workflow_state_action_delete,
|
link_workflow_template_state_action_delete,
|
||||||
link_setup_workflow_state_action_edit,
|
link_workflow_template_state_action_edit,
|
||||||
link_setup_workflow_state_action_list,
|
link_workflow_template_state_action_list,
|
||||||
link_setup_workflow_state_action_selection,
|
link_workflow_template_state_action_selection,
|
||||||
link_setup_workflow_state_create, link_setup_workflow_state_delete,
|
link_workflow_template_state_create, link_workflow_template_state_delete,
|
||||||
link_setup_workflow_state_edit, link_setup_workflow_transitions,
|
link_workflow_template_state_edit, link_workflow_template_transition_list,
|
||||||
link_setup_workflow_transition_create,
|
link_workflow_template_transition_create,
|
||||||
link_setup_workflow_transition_delete, link_setup_workflow_transition_edit,
|
link_workflow_template_transition_delete, link_workflow_template_transition_edit,
|
||||||
link_setup_workflow_transition_field_create,
|
link_workflow_template_transition_field_create,
|
||||||
link_setup_workflow_transition_field_delete,
|
link_workflow_template_transition_field_delete,
|
||||||
link_setup_workflow_transition_field_edit,
|
link_workflow_template_transition_field_edit,
|
||||||
link_setup_workflow_transition_field_list,
|
link_workflow_template_transition_field_list,
|
||||||
link_tool_launch_all_workflows, link_workflow_instance_detail,
|
link_tool_launch_workflows, link_workflow_instance_detail,
|
||||||
link_workflow_instance_transition, link_workflow_runtime_proxy_document_list,
|
link_workflow_instance_transition, link_workflow_runtime_proxy_document_list,
|
||||||
link_workflow_runtime_proxy_list, link_workflow_preview,
|
link_workflow_runtime_proxy_list, link_workflow_template_preview,
|
||||||
link_workflow_runtime_proxy_state_document_list, link_workflow_runtime_proxy_state_list,
|
link_workflow_runtime_proxy_state_document_list, link_workflow_runtime_proxy_state_list,
|
||||||
link_workflow_transition_events
|
link_workflow_template_transition_events
|
||||||
)
|
)
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_workflow_delete, permission_workflow_edit,
|
permission_workflow_delete, permission_workflow_edit,
|
||||||
@@ -319,49 +319,49 @@ class DocumentStatesApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
menu_facet.bind_links(
|
menu_facet.bind_links(
|
||||||
links=(link_document_workflow_instance_list,), sources=(Document,)
|
links=(link_workflow_instance_list,), sources=(Document,)
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_list_facet.bind_links(
|
menu_list_facet.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_acl_list, link_events_for_object,
|
link_acl_list, link_events_for_object,
|
||||||
link_object_event_types_user_subcriptions_list,
|
link_object_event_types_user_subcriptions_list,
|
||||||
link_setup_workflow_document_types,
|
link_workflow_template_document_types,
|
||||||
link_setup_workflow_states, link_setup_workflow_transitions,
|
link_workflow_template_state_list, link_workflow_template_transition_list,
|
||||||
link_workflow_preview
|
link_workflow_template_preview
|
||||||
), sources=(Workflow,)
|
), sources=(Workflow,)
|
||||||
)
|
)
|
||||||
menu_list_facet.bind_links(
|
menu_list_facet.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_setup_document_type_workflows,
|
link_document_type_workflow_templates,
|
||||||
), sources=(DocumentType,)
|
), sources=(DocumentType,)
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_main.bind_links(links=(link_workflow_runtime_proxy_list,), position=10)
|
menu_main.bind_links(links=(link_workflow_runtime_proxy_list,), position=10)
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_setup_workflow_delete, link_setup_workflow_edit
|
link_workflow_template_delete, link_workflow_template_edit
|
||||||
), sources=(Workflow,)
|
), sources=(Workflow,)
|
||||||
)
|
)
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_setup_workflow_state_edit,
|
link_workflow_template_state_edit,
|
||||||
link_setup_workflow_state_action_list,
|
link_workflow_template_state_action_list,
|
||||||
link_setup_workflow_state_delete
|
link_workflow_template_state_delete
|
||||||
), sources=(WorkflowState,)
|
), sources=(WorkflowState,)
|
||||||
)
|
)
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_setup_workflow_transition_edit,
|
link_workflow_template_transition_edit,
|
||||||
link_workflow_transition_events,
|
link_workflow_template_transition_events,
|
||||||
link_setup_workflow_transition_field_list, link_acl_list,
|
link_workflow_template_transition_field_list, link_acl_list,
|
||||||
link_setup_workflow_transition_delete
|
link_workflow_template_transition_delete
|
||||||
), sources=(WorkflowTransition,)
|
), sources=(WorkflowTransition,)
|
||||||
)
|
)
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_setup_workflow_transition_field_delete,
|
link_workflow_template_transition_field_delete,
|
||||||
link_setup_workflow_transition_field_edit
|
link_workflow_template_transition_field_edit
|
||||||
), sources=(WorkflowTransitionField,)
|
), sources=(WorkflowTransitionField,)
|
||||||
)
|
)
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
@@ -384,21 +384,21 @@ class DocumentStatesApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_setup_workflow_state_action_edit,
|
link_workflow_template_state_action_edit,
|
||||||
link_object_error_list,
|
link_object_error_list,
|
||||||
link_setup_workflow_state_action_delete,
|
link_workflow_template_state_action_delete,
|
||||||
), sources=(WorkflowStateAction,)
|
), sources=(WorkflowStateAction,)
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(link_setup_workflow_list, link_setup_workflow_create),
|
links=(link_workflow_template_list, link_workflow_template_create),
|
||||||
sources=(
|
sources=(
|
||||||
Workflow, 'document_states:setup_workflow_create',
|
Workflow, 'document_states:workflow_template_create',
|
||||||
'document_states:setup_workflow_list'
|
'document_states:workflow_template_list'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(link_setup_workflow_transition_field_create,),
|
links=(link_workflow_template_transition_field_create,),
|
||||||
sources=(
|
sources=(
|
||||||
WorkflowTransition,
|
WorkflowTransition,
|
||||||
)
|
)
|
||||||
@@ -410,31 +410,31 @@ class DocumentStatesApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(link_setup_workflow_state_action_selection,),
|
links=(link_workflow_template_state_action_selection,),
|
||||||
sources=(
|
sources=(
|
||||||
WorkflowState,
|
WorkflowState,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_setup_workflow_transition_create,
|
link_workflow_template_transition_create,
|
||||||
), sources=(
|
), sources=(
|
||||||
WorkflowTransition,
|
WorkflowTransition,
|
||||||
'document_states:setup_workflow_transition_list',
|
'document_states:workflow_template_transition_list',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_setup_workflow_state_create,
|
link_workflow_template_state_create,
|
||||||
), sources=(
|
), sources=(
|
||||||
WorkflowState,
|
WorkflowState,
|
||||||
'document_states:setup_workflow_state_list',
|
'document_states:workflow_template_state_list',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_setup.bind_links(links=(link_setup_workflow_list,))
|
menu_setup.bind_links(links=(link_workflow_template_list,))
|
||||||
|
|
||||||
menu_tools.bind_links(links=(link_tool_launch_all_workflows,))
|
menu_tools.bind_links(links=(link_tool_launch_workflows,))
|
||||||
|
|
||||||
post_save.connect(
|
post_save.connect(
|
||||||
dispatch_uid='workflows_handler_launch_workflow',
|
dispatch_uid='workflows_handler_launch_workflow',
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.urls import reverse
|
from django.utils.html import format_html_join
|
||||||
from django.utils.html import format_html_join, mark_safe
|
|
||||||
|
|
||||||
|
|
||||||
def widget_transition_events(transition):
|
def widget_transition_events(transition):
|
||||||
|
|||||||
@@ -1,45 +1,44 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from mayan.apps.appearance.classes import Icon
|
from mayan.apps.appearance.classes import Icon
|
||||||
from mayan.apps.documents.icons import icon_document_type
|
from mayan.apps.documents.icons import icon_document, icon_document_type
|
||||||
|
|
||||||
icon_workflow = Icon(driver_name='fontawesome', symbol='sitemap')
|
icon_workflow = Icon(driver_name='fontawesome', symbol='sitemap')
|
||||||
|
|
||||||
icon_document_type_workflow_list = icon_workflow
|
icon_tool_launch_workflows = icon_workflow
|
||||||
|
|
||||||
icon_document_workflow_instance_list = Icon(
|
icon_document_type_workflow_list = icon_workflow
|
||||||
driver_name='fontawesome', symbol='sitemap'
|
icon_workflow_template_create = Icon(
|
||||||
)
|
|
||||||
icon_setup_workflow_list = Icon(driver_name='fontawesome', symbol='sitemap')
|
|
||||||
icon_tool_launch_all_workflows = Icon(
|
|
||||||
driver_name='fontawesome', symbol='sitemap'
|
|
||||||
)
|
|
||||||
icon_workflow_create = Icon(
|
|
||||||
driver_name='fontawesome-dual', primary_symbol='sitemap',
|
driver_name='fontawesome-dual', primary_symbol='sitemap',
|
||||||
secondary_symbol='plus'
|
secondary_symbol='plus'
|
||||||
)
|
)
|
||||||
icon_workflow_delete = Icon(driver_name='fontawesome', symbol='times')
|
icon_workflow_template_delete = Icon(driver_name='fontawesome', symbol='times')
|
||||||
icon_workflow_document_type_list = icon_document_type
|
icon_workflow_template_document_type_list = icon_document_type
|
||||||
icon_workflow_edit = Icon(driver_name='fontawesome', symbol='pencil-alt')
|
icon_workflow_template_edit = Icon(
|
||||||
icon_workflow_list = Icon(driver_name='fontawesome', symbol='sitemap')
|
driver_name='fontawesome', symbol='pencil-alt'
|
||||||
icon_workflow_preview = Icon(driver_name='fontawesome', symbol='eye')
|
|
||||||
|
|
||||||
icon_workflow_instance_detail = Icon(
|
|
||||||
driver_name='fontawesome', symbol='sitemap'
|
|
||||||
)
|
)
|
||||||
|
icon_workflow_template_list = icon_workflow
|
||||||
|
icon_workflow_template_preview = Icon(driver_name='fontawesome', symbol='eye')
|
||||||
|
|
||||||
|
# Workflow instances
|
||||||
|
|
||||||
|
icon_workflow_instance_detail = icon_workflow
|
||||||
|
icon_workflow_instance_list = icon_workflow
|
||||||
icon_workflow_instance_transition = Icon(
|
icon_workflow_instance_transition = Icon(
|
||||||
driver_name='fontawesome', symbol='arrows-alt-h'
|
driver_name='fontawesome', symbol='arrows-alt-h'
|
||||||
)
|
)
|
||||||
|
|
||||||
icon_workflow_runtime_proxy_document_list = icon_document_type
|
# Workflow runtime proxies
|
||||||
icon_workflow_runtime_proxy_list = Icon(
|
|
||||||
driver_name='fontawesome', symbol='sitemap'
|
icon_workflow_runtime_proxy_document_list = icon_document
|
||||||
)
|
icon_workflow_runtime_proxy_list = icon_workflow
|
||||||
icon_workflow_runtime_proxy_state_document_list = icon_document_type
|
icon_workflow_runtime_proxy_state_document_list = icon_document
|
||||||
icon_workflow_runtime_proxy_state_list = Icon(
|
icon_workflow_runtime_proxy_state_list = Icon(
|
||||||
driver_name='fontawesome', symbol='circle'
|
driver_name='fontawesome', symbol='circle'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Workflow transition states
|
||||||
|
|
||||||
icon_workflow_state_action_delete = Icon(
|
icon_workflow_state_action_delete = Icon(
|
||||||
driver_name='fontawesome', symbol='times'
|
driver_name='fontawesome', symbol='times'
|
||||||
)
|
)
|
||||||
@@ -57,6 +56,8 @@ icon_workflow_state_create = Icon(
|
|||||||
icon_workflow_state_delete = Icon(driver_name='fontawesome', symbol='times')
|
icon_workflow_state_delete = Icon(driver_name='fontawesome', symbol='times')
|
||||||
icon_workflow_state_edit = Icon(driver_name='fontawesome', symbol='pencil-alt')
|
icon_workflow_state_edit = Icon(driver_name='fontawesome', symbol='pencil-alt')
|
||||||
|
|
||||||
|
# Workflow transition state actions
|
||||||
|
|
||||||
icon_workflow_state_action = Icon(driver_name='fontawesome', symbol='code')
|
icon_workflow_state_action = Icon(driver_name='fontawesome', symbol='code')
|
||||||
icon_workflow_state_action_delete = Icon(
|
icon_workflow_state_action_delete = Icon(
|
||||||
driver_name='fontawesome', symbol='times'
|
driver_name='fontawesome', symbol='times'
|
||||||
@@ -71,6 +72,9 @@ icon_workflow_state_action_selection = Icon(
|
|||||||
icon_workflow_state_action_list = Icon(
|
icon_workflow_state_action_list = Icon(
|
||||||
driver_name='fontawesome', symbol='code'
|
driver_name='fontawesome', symbol='code'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Workflow transitions
|
||||||
|
|
||||||
icon_workflow_transition = Icon(
|
icon_workflow_transition = Icon(
|
||||||
driver_name='fontawesome', symbol='arrows-alt-h'
|
driver_name='fontawesome', symbol='arrows-alt-h'
|
||||||
)
|
)
|
||||||
@@ -85,7 +89,11 @@ icon_workflow_transition_edit = Icon(
|
|||||||
driver_name='fontawesome', symbol='pencil-alt'
|
driver_name='fontawesome', symbol='pencil-alt'
|
||||||
)
|
)
|
||||||
|
|
||||||
icon_workflow_transition_field = Icon(driver_name='fontawesome', symbol='table')
|
# Workflow transition fields
|
||||||
|
|
||||||
|
icon_workflow_transition_field = Icon(
|
||||||
|
driver_name='fontawesome', symbol='table'
|
||||||
|
)
|
||||||
icon_workflow_transition_field_delete = Icon(
|
icon_workflow_transition_field_delete = Icon(
|
||||||
driver_name='fontawesome', symbol='times'
|
driver_name='fontawesome', symbol='times'
|
||||||
)
|
)
|
||||||
@@ -99,7 +107,6 @@ icon_workflow_transition_field_create = Icon(
|
|||||||
icon_workflow_transition_field_list = Icon(
|
icon_workflow_transition_field_list = Icon(
|
||||||
driver_name='fontawesome', symbol='table'
|
driver_name='fontawesome', symbol='table'
|
||||||
)
|
)
|
||||||
|
|
||||||
icon_workflow_transition_triggers = Icon(
|
icon_workflow_transition_triggers = Icon(
|
||||||
driver_name='fontawesome', symbol='bolt'
|
driver_name='fontawesome', symbol='bolt'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,179 +11,185 @@ from .permissions import (
|
|||||||
permission_workflow_view,
|
permission_workflow_view,
|
||||||
)
|
)
|
||||||
|
|
||||||
link_setup_document_type_workflows = Link(
|
# Workflow templates
|
||||||
|
|
||||||
|
link_document_type_workflow_templates = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_document_type_workflow_list',
|
icon_class_path='mayan.apps.document_states.icons.icon_document_type_workflow_list',
|
||||||
permissions=(permission_document_type_edit,), text=_('Workflows'),
|
permissions=(permission_document_type_edit,), text=_('Workflows'),
|
||||||
view='document_states:document_type_workflows',
|
view='document_states:document_type_workflow_templates',
|
||||||
)
|
)
|
||||||
link_setup_workflow_create = Link(
|
link_workflow_template_create = Link(
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_create',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_template_create',
|
||||||
permissions=(permission_workflow_create,),
|
permissions=(permission_workflow_create,),
|
||||||
text=_('Create workflow'), view='document_states:setup_workflow_create'
|
text=_('Create workflow'), view='document_states:workflow_template_create'
|
||||||
)
|
)
|
||||||
link_setup_workflow_delete = Link(
|
link_workflow_template_delete = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_delete',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_template_delete',
|
||||||
permissions=(permission_workflow_delete,),
|
permissions=(permission_workflow_delete,),
|
||||||
tags='dangerous', text=_('Delete'),
|
tags='dangerous', text=_('Delete'),
|
||||||
view='document_states:setup_workflow_delete',
|
view='document_states:workflow_template_delete',
|
||||||
)
|
)
|
||||||
link_setup_workflow_document_types = Link(
|
link_workflow_template_document_types = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_document_type_list',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_template_document_type_list',
|
||||||
permissions=(permission_workflow_edit,), text=_('Document types'),
|
permissions=(permission_workflow_edit,), text=_('Document types'),
|
||||||
view='document_states:setup_workflow_document_types',
|
view='document_states:workflow_template_document_types',
|
||||||
)
|
)
|
||||||
link_setup_workflow_edit = Link(
|
link_workflow_template_edit = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_edit',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_template_edit',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
text=_('Edit'), view='document_states:setup_workflow_edit',
|
text=_('Edit'), view='document_states:workflow_template_edit',
|
||||||
)
|
)
|
||||||
link_setup_workflow_list = Link(
|
link_workflow_template_list = Link(
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_setup_workflow_list',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_template_list',
|
||||||
permissions=(permission_workflow_view,), text=_('Workflows'),
|
permissions=(permission_workflow_view,), text=_('Workflows'),
|
||||||
view='document_states:setup_workflow_list'
|
view='document_states:workflow_template_list'
|
||||||
)
|
)
|
||||||
link_setup_workflow_state_action_delete = Link(
|
link_workflow_template_preview = Link(
|
||||||
|
args='resolved_object.pk',
|
||||||
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_template_preview',
|
||||||
|
permissions=(permission_workflow_view,),
|
||||||
|
text=_('Preview'), view='document_states:workflow_template_preview'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Workflow template state actions
|
||||||
|
|
||||||
|
link_workflow_template_state_action_delete = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_action_delete',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_action_delete',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
tags='dangerous', text=_('Delete'),
|
tags='dangerous', text=_('Delete'),
|
||||||
view='document_states:setup_workflow_state_action_delete',
|
view='document_states:workflow_template_state_action_delete',
|
||||||
)
|
)
|
||||||
link_setup_workflow_state_action_edit = Link(
|
link_workflow_template_state_action_edit = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_action_edit',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_action_edit',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
text=_('Edit'), view='document_states:setup_workflow_state_action_edit',
|
text=_('Edit'), view='document_states:workflow_template_state_action_edit',
|
||||||
)
|
)
|
||||||
link_setup_workflow_state_action_list = Link(
|
link_workflow_template_state_action_list = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_action_list',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_action_list',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
text=_('Actions'),
|
text=_('Actions'),
|
||||||
view='document_states:setup_workflow_state_action_list',
|
view='document_states:workflow_template_state_action_list',
|
||||||
)
|
)
|
||||||
link_setup_workflow_state_action_selection = Link(
|
link_workflow_template_state_action_selection = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_action',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_action',
|
||||||
permissions=(permission_workflow_edit,), text=_('Create action'),
|
permissions=(permission_workflow_edit,), text=_('Create action'),
|
||||||
view='document_states:setup_workflow_state_action_selection',
|
view='document_states:workflow_template_state_action_selection',
|
||||||
)
|
)
|
||||||
link_setup_workflow_state_create = Link(
|
|
||||||
|
# Workflow template states
|
||||||
|
|
||||||
|
link_workflow_template_state_create = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_create',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_create',
|
||||||
permissions=(permission_workflow_edit,), text=_('Create state'),
|
permissions=(permission_workflow_edit,), text=_('Create state'),
|
||||||
view='document_states:setup_workflow_state_create',
|
view='document_states:workflow_template_state_create',
|
||||||
)
|
)
|
||||||
link_setup_workflow_state_delete = Link(
|
link_workflow_template_state_delete = Link(
|
||||||
args='object.pk',
|
args='object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_delete',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_delete',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
tags='dangerous', text=_('Delete'),
|
tags='dangerous', text=_('Delete'),
|
||||||
view='document_states:setup_workflow_state_delete',
|
view='document_states:workflow_template_state_delete',
|
||||||
)
|
)
|
||||||
link_setup_workflow_state_edit = Link(
|
link_workflow_template_state_edit = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_edit',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state_edit',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
text=_('Edit'), view='document_states:setup_workflow_state_edit',
|
text=_('Edit'), view='document_states:workflow_template_state_edit',
|
||||||
)
|
)
|
||||||
link_setup_workflow_states = Link(
|
link_workflow_template_state_list = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_state',
|
||||||
permissions=(permission_workflow_view,), text=_('States'),
|
permissions=(permission_workflow_view,), text=_('States'),
|
||||||
view='document_states:setup_workflow_state_list',
|
view='document_states:workflow_template_state_list',
|
||||||
)
|
)
|
||||||
link_setup_workflow_transition_create = Link(
|
|
||||||
|
# Workflow template transitions
|
||||||
|
|
||||||
|
link_workflow_template_transition_create = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_create',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_create',
|
||||||
permissions=(permission_workflow_edit,), text=_('Create transition'),
|
permissions=(permission_workflow_edit,), text=_('Create transition'),
|
||||||
view='document_states:setup_workflow_transition_create',
|
view='document_states:workflow_template_transition_create',
|
||||||
)
|
)
|
||||||
link_setup_workflow_transition_delete = Link(
|
link_workflow_template_transition_delete = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_delete',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_delete',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
tags='dangerous', text=_('Delete'),
|
tags='dangerous', text=_('Delete'),
|
||||||
view='document_states:setup_workflow_transition_delete',
|
view='document_states:workflow_template_transition_delete',
|
||||||
)
|
)
|
||||||
link_setup_workflow_transition_edit = Link(
|
link_workflow_template_transition_edit = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_edit',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_edit',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
text=_('Edit'), view='document_states:setup_workflow_transition_edit',
|
text=_('Edit'), view='document_states:workflow_template_transition_edit',
|
||||||
)
|
)
|
||||||
link_setup_workflow_transitions = Link(
|
link_workflow_template_transition_events = Link(
|
||||||
args='resolved_object.pk',
|
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition',
|
|
||||||
permissions=(permission_workflow_view,), text=_('Transitions'),
|
|
||||||
view='document_states:setup_workflow_transition_list',
|
|
||||||
)
|
|
||||||
link_workflow_transition_events = Link(
|
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_triggers',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_triggers',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
text=_('Transition triggers'),
|
text=_('Transition triggers'),
|
||||||
view='document_states:setup_workflow_transition_events'
|
view='document_states:workflow_template_transition_events'
|
||||||
|
)
|
||||||
|
link_workflow_template_transition_list = Link(
|
||||||
|
args='resolved_object.pk',
|
||||||
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition',
|
||||||
|
permissions=(permission_workflow_view,), text=_('Transitions'),
|
||||||
|
view='document_states:workflow_template_transition_list',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Workflow transition fields
|
# Workflow transition fields
|
||||||
link_setup_workflow_transition_field_create = Link(
|
|
||||||
|
link_workflow_template_transition_field_create = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field',
|
||||||
permissions=(permission_workflow_edit,), text=_('Create field'),
|
permissions=(permission_workflow_edit,), text=_('Create field'),
|
||||||
view='document_states:setup_workflow_transition_field_create',
|
view='document_states:workflow_template_transition_field_create',
|
||||||
)
|
)
|
||||||
link_setup_workflow_transition_field_delete = Link(
|
link_workflow_template_transition_field_delete = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field_delete',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field_delete',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
tags='dangerous', text=_('Delete'),
|
tags='dangerous', text=_('Delete'),
|
||||||
view='document_states:setup_workflow_transition_field_delete',
|
view='document_states:workflow_template_transition_field_delete',
|
||||||
)
|
)
|
||||||
link_setup_workflow_transition_field_edit = Link(
|
link_workflow_template_transition_field_edit = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field_edit',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field_edit',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
text=_('Edit'), view='document_states:setup_workflow_transition_field_edit',
|
text=_('Edit'), view='document_states:workflow_template_transition_field_edit',
|
||||||
)
|
)
|
||||||
link_setup_workflow_transition_field_list = Link(
|
link_workflow_template_transition_field_list = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field_list',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field_list',
|
||||||
permissions=(permission_workflow_edit,),
|
permissions=(permission_workflow_edit,),
|
||||||
text=_('Fields'),
|
text=_('Fields'),
|
||||||
view='document_states:setup_workflow_transition_field_list',
|
view='document_states:workflow_template_transition_field_list',
|
||||||
)
|
|
||||||
|
|
||||||
link_workflow_preview = Link(
|
|
||||||
args='resolved_object.pk',
|
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_preview',
|
|
||||||
permissions=(permission_workflow_view,),
|
|
||||||
text=_('Preview'), view='document_states:workflow_preview'
|
|
||||||
)
|
|
||||||
link_tool_launch_all_workflows = Link(
|
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_tool_launch_all_workflows',
|
|
||||||
permissions=(permission_workflow_tools,),
|
|
||||||
text=_('Launch all workflows'),
|
|
||||||
view='document_states:tool_launch_all_workflows'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Document workflow instances
|
# Document workflow instances
|
||||||
link_document_workflow_instance_list = Link(
|
|
||||||
args='resolved_object.pk',
|
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_document_workflow_instance_list',
|
|
||||||
permissions=(permission_workflow_view,), text=_('Workflows'),
|
|
||||||
view='document_states:document_workflow_instance_list',
|
|
||||||
)
|
|
||||||
link_workflow_instance_detail = Link(
|
link_workflow_instance_detail = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_instance_detail',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_instance_detail',
|
||||||
permissions=(permission_workflow_view,),
|
permissions=(permission_workflow_view,),
|
||||||
text=_('Detail'), view='document_states:workflow_instance_detail',
|
text=_('Detail'), view='document_states:workflow_instance_detail',
|
||||||
)
|
)
|
||||||
|
link_workflow_instance_list = Link(
|
||||||
|
args='resolved_object.pk',
|
||||||
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_instance_list',
|
||||||
|
permissions=(permission_workflow_view,), text=_('Workflows'),
|
||||||
|
view='document_states:workflow_instance_list',
|
||||||
|
)
|
||||||
link_workflow_instance_transition = Link(
|
link_workflow_instance_transition = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_instance_transition',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_instance_transition',
|
||||||
@@ -192,28 +198,38 @@ link_workflow_instance_transition = Link(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Runtime proxies
|
# Runtime proxies
|
||||||
|
|
||||||
link_workflow_runtime_proxy_document_list = Link(
|
link_workflow_runtime_proxy_document_list = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_runtime_proxy_document_list',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_runtime_proxy_document_list',
|
||||||
permissions=(permission_workflow_view,),
|
permissions=(permission_workflow_view,),
|
||||||
text=_('Workflow documents'),
|
text=_('Workflow documents'),
|
||||||
view='document_states:workflow_document_list',
|
view='document_states:workflow_runtime_proxy_document_list',
|
||||||
)
|
)
|
||||||
link_workflow_runtime_proxy_list = Link(
|
link_workflow_runtime_proxy_list = Link(
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_runtime_proxy_list',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_runtime_proxy_list',
|
||||||
permissions=(permission_workflow_view,),
|
permissions=(permission_workflow_view,),
|
||||||
text=_('Workflows'), view='document_states:workflow_list'
|
text=_('Workflows'), view='document_states:workflow_runtime_proxy_list'
|
||||||
)
|
)
|
||||||
link_workflow_runtime_proxy_state_document_list = Link(
|
link_workflow_runtime_proxy_state_document_list = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_runtime_proxy_state_document_list',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_runtime_proxy_state_document_list',
|
||||||
permissions=(permission_workflow_view,),
|
permissions=(permission_workflow_view,),
|
||||||
text=_('State documents'),
|
text=_('State documents'),
|
||||||
view='document_states:workflow_state_document_list',
|
view='document_states:workflow_runtime_proxy_state_document_list',
|
||||||
)
|
)
|
||||||
link_workflow_runtime_proxy_state_list = Link(
|
link_workflow_runtime_proxy_state_list = Link(
|
||||||
args='resolved_object.pk',
|
args='resolved_object.pk',
|
||||||
icon_class_path='mayan.apps.document_states.icons.icon_workflow_runtime_proxy_state_list',
|
icon_class_path='mayan.apps.document_states.icons.icon_workflow_runtime_proxy_state_list',
|
||||||
permissions=(permission_workflow_view,),
|
permissions=(permission_workflow_view,),
|
||||||
text=_('States'), view='document_states:workflow_state_list',
|
text=_('States'), view='document_states:workflow_runtime_proxy_state_list',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
|
||||||
|
link_tool_launch_workflows = Link(
|
||||||
|
icon_class_path='mayan.apps.document_states.icons.icon_tool_launch_workflows',
|
||||||
|
permissions=(permission_workflow_tools,),
|
||||||
|
text=_('Launch all workflows'),
|
||||||
|
view='document_states:tool_launch_workflows'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import os
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
namespace = Namespace(label=_('Workflows'), name='document_states')
|
namespace = Namespace(label=_('Workflows'), name='document_states')
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.utils.module_loading import import_string
|
from mayan.apps.storage.utils import get_storage_subclass
|
||||||
|
|
||||||
from .settings import (
|
from .settings import (
|
||||||
setting_workflowimagecache_storage,
|
setting_workflowimagecache_storage,
|
||||||
setting_workflowimagecache_storage_arguments
|
setting_workflowimagecache_storage_arguments
|
||||||
)
|
)
|
||||||
|
|
||||||
storage_workflowimagecache = import_string(
|
storage_workflowimagecache = get_storage_subclass(
|
||||||
dotted_path=setting_workflowimagecache_storage.value
|
dotted_path=setting_workflowimagecache_storage.value
|
||||||
)(**setting_workflowimagecache_storage_arguments.value)
|
)(**setting_workflowimagecache_storage_arguments.value)
|
||||||
|
|||||||
@@ -24,4 +24,3 @@ TEST_WORKFLOW_TRANSITION_LABEL_EDITED = 'test transition label edited'
|
|||||||
TEST_INDEX_TEMPLATE_METADATA_EXPRESSION = '{{{{ document.workflow.{}.get_current_state }}}}'.format(
|
TEST_INDEX_TEMPLATE_METADATA_EXPRESSION = '{{{{ document.workflow.{}.get_current_state }}}}'.format(
|
||||||
TEST_WORKFLOW_INTERNAL_NAME
|
TEST_WORKFLOW_INTERNAL_NAME
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -38,19 +38,19 @@ class WorkflowStateViewTestMixin(object):
|
|||||||
data.update(extra_data)
|
data.update(extra_data)
|
||||||
|
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_state_create',
|
viewname='document_states:workflow_template_state_create',
|
||||||
kwargs={'pk': self.test_workflow.pk}, data=data
|
kwargs={'pk': self.test_workflow.pk}, data=data
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_test_workflow_state_delete_view(self):
|
def _request_test_workflow_state_delete_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_state_delete',
|
viewname='document_states:workflow_template_state_delete',
|
||||||
kwargs={'pk': self.test_workflow_state_1.pk}
|
kwargs={'pk': self.test_workflow_state_1.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_test_workflow_state_edit_view(self):
|
def _request_test_workflow_state_edit_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_state_edit',
|
viewname='document_states:workflow_template_state_edit',
|
||||||
kwargs={'pk': self.test_workflow_state_1.pk}, data={
|
kwargs={'pk': self.test_workflow_state_1.pk}, data={
|
||||||
'label': TEST_WORKFLOW_STATE_LABEL_EDITED
|
'label': TEST_WORKFLOW_STATE_LABEL_EDITED
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ class WorkflowStateViewTestMixin(object):
|
|||||||
|
|
||||||
def _request_test_workflow_state_list_view(self):
|
def _request_test_workflow_state_list_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='document_states:setup_workflow_state_list',
|
viewname='document_states:workflow_template_state_list',
|
||||||
kwargs={'pk': self.test_workflow.pk}
|
kwargs={'pk': self.test_workflow.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ class WorkflowTestMixin(object):
|
|||||||
class WorkflowTransitionViewTestMixin(object):
|
class WorkflowTransitionViewTestMixin(object):
|
||||||
def _request_test_workflow_transition_create_view(self):
|
def _request_test_workflow_transition_create_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_transition_create',
|
viewname='document_states:workflow_template_transition_create',
|
||||||
kwargs={'pk': self.test_workflow.pk}, data={
|
kwargs={'pk': self.test_workflow.pk}, data={
|
||||||
'label': TEST_WORKFLOW_TRANSITION_LABEL,
|
'label': TEST_WORKFLOW_TRANSITION_LABEL,
|
||||||
'origin_state': self.test_workflow_state_1.pk,
|
'origin_state': self.test_workflow_state_1.pk,
|
||||||
@@ -130,13 +130,13 @@ class WorkflowTransitionViewTestMixin(object):
|
|||||||
|
|
||||||
def _request_test_workflow_transition_delete_view(self):
|
def _request_test_workflow_transition_delete_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_transition_delete',
|
viewname='document_states:workflow_template_transition_delete',
|
||||||
kwargs={'pk': self.test_workflow_transition.pk}
|
kwargs={'pk': self.test_workflow_transition.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_test_workflow_transition_edit_view(self):
|
def _request_test_workflow_transition_edit_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_transition_edit',
|
viewname='document_states:workflow_template_transition_edit',
|
||||||
kwargs={'pk': self.test_workflow_transition.pk}, data={
|
kwargs={'pk': self.test_workflow_transition.pk}, data={
|
||||||
'label': TEST_WORKFLOW_TRANSITION_LABEL_EDITED,
|
'label': TEST_WORKFLOW_TRANSITION_LABEL_EDITED,
|
||||||
'origin_state': self.test_workflow_state_1.pk,
|
'origin_state': self.test_workflow_state_1.pk,
|
||||||
@@ -146,7 +146,7 @@ class WorkflowTransitionViewTestMixin(object):
|
|||||||
|
|
||||||
def _request_test_workflow_transition_list_view(self):
|
def _request_test_workflow_transition_list_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='document_states:setup_workflow_transition_list',
|
viewname='document_states:workflow_template_transition_list',
|
||||||
kwargs={'pk': self.test_workflow.pk}
|
kwargs={'pk': self.test_workflow.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ class WorkflowTransitionViewTestMixin(object):
|
|||||||
class WorkflowViewTestMixin(object):
|
class WorkflowViewTestMixin(object):
|
||||||
def _request_test_workflow_create_view(self):
|
def _request_test_workflow_create_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_create', data={
|
viewname='document_states:workflow_template_create', data={
|
||||||
'label': TEST_WORKFLOW_LABEL,
|
'label': TEST_WORKFLOW_LABEL,
|
||||||
'internal_name': TEST_WORKFLOW_INTERNAL_NAME,
|
'internal_name': TEST_WORKFLOW_INTERNAL_NAME,
|
||||||
}
|
}
|
||||||
@@ -171,14 +171,14 @@ class WorkflowViewTestMixin(object):
|
|||||||
|
|
||||||
def _request_test_workflow_delete_view(self):
|
def _request_test_workflow_delete_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_delete', kwargs={
|
viewname='document_states:workflow_template_delete', kwargs={
|
||||||
'pk': self.test_workflow.pk
|
'pk': self.test_workflow.pk
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_test_workflow_edit_view(self):
|
def _request_test_workflow_edit_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_edit', kwargs={
|
viewname='document_states:workflow_template_edit', kwargs={
|
||||||
'pk': self.test_workflow.pk,
|
'pk': self.test_workflow.pk,
|
||||||
}, data={
|
}, data={
|
||||||
'label': TEST_WORKFLOW_LABEL_EDITED,
|
'label': TEST_WORKFLOW_LABEL_EDITED,
|
||||||
@@ -188,12 +188,12 @@ class WorkflowViewTestMixin(object):
|
|||||||
|
|
||||||
def _request_test_workflow_list_view(self):
|
def _request_test_workflow_list_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='document_states:setup_workflow_list',
|
viewname='document_states:workflow_template_list',
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_test_workflow_preview_view(self):
|
def _request_test_workflow_template_preview_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='document_states:workflow_preview', kwargs={
|
viewname='document_states:workflow_template_preview', kwargs={
|
||||||
'pk': self.test_workflow.pk,
|
'pk': self.test_workflow.pk,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class WorkflowStateActionViewTestCase(WorkflowStateActionTestMixin, WorkflowTest
|
|||||||
|
|
||||||
def _request_test_document_state_action_view(self):
|
def _request_test_document_state_action_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='document_states:setup_workflow_state_action_list',
|
viewname='document_states:workflow_template_state_action_list',
|
||||||
kwargs={'pk': self.test_workflow_state.pk}
|
kwargs={'pk': self.test_workflow_state.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ class WorkflowTransitionEventViewTestCase(
|
|||||||
):
|
):
|
||||||
def _request_test_workflow_transition_event_list_view(self):
|
def _request_test_workflow_transition_event_list_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='document_states:setup_workflow_transition_events',
|
viewname='document_states:workflow_template_transition_events',
|
||||||
kwargs={'pk': self.test_workflow_transition.pk}
|
kwargs={'pk': self.test_workflow_transition.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ class WorkflowTransitionFieldViewTestCase(
|
|||||||
|
|
||||||
def _request_test_workflow_transition_field_list_view(self):
|
def _request_test_workflow_transition_field_list_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='document_states:setup_workflow_transition_field_list',
|
viewname='document_states:workflow_template_transition_field_list',
|
||||||
kwargs={'pk': self.test_workflow_transition.pk}
|
kwargs={'pk': self.test_workflow_transition.pk}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ class WorkflowTransitionFieldViewTestCase(
|
|||||||
|
|
||||||
def _request_workflow_transition_field_create_view(self):
|
def _request_workflow_transition_field_create_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_transition_field_create',
|
viewname='document_states:workflow_template_transition_field_create',
|
||||||
kwargs={'pk': self.test_workflow_transition.pk},
|
kwargs={'pk': self.test_workflow_transition.pk},
|
||||||
data={
|
data={
|
||||||
'field_type': TEST_WORKFLOW_TRANSITION_FIELD_TYPE,
|
'field_type': TEST_WORKFLOW_TRANSITION_FIELD_TYPE,
|
||||||
@@ -324,7 +324,7 @@ class WorkflowTransitionFieldViewTestCase(
|
|||||||
|
|
||||||
def _request_workflow_transition_field_delete_view(self):
|
def _request_workflow_transition_field_delete_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_transition_field_delete',
|
viewname='document_states:workflow_template_transition_field_delete',
|
||||||
kwargs={'pk': self.test_workflow_transition_field.pk},
|
kwargs={'pk': self.test_workflow_transition_field.pk},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -93,31 +93,31 @@ class WorkflowViewTestCase(
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, text=self.test_workflow.label)
|
self.assertContains(response, text=self.test_workflow.label)
|
||||||
|
|
||||||
def test_workflow_preview_view_no_access(self):
|
def test_workflow_template_preview_view_no_access(self):
|
||||||
self._create_test_workflow()
|
self._create_test_workflow()
|
||||||
|
|
||||||
response = self._request_test_workflow_preview_view()
|
response = self._request_test_workflow_template_preview_view()
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
self.assertTrue(self.test_workflow in Workflow.objects.all())
|
self.assertTrue(self.test_workflow in Workflow.objects.all())
|
||||||
|
|
||||||
def test_workflow_preview_view_with_access(self):
|
def test_workflow_template_preview_view_with_access(self):
|
||||||
self._create_test_workflow()
|
self._create_test_workflow()
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_workflow, permission=permission_workflow_view
|
obj=self.test_workflow, permission=permission_workflow_view
|
||||||
)
|
)
|
||||||
response = self._request_test_workflow_preview_view()
|
response = self._request_test_workflow_template_preview_view()
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowToolViewTestCase(WorkflowTestMixin, GenericDocumentViewTestCase):
|
class WorkflowToolViewTestCase(WorkflowTestMixin, GenericDocumentViewTestCase):
|
||||||
def _request_workflow_launch_view(self):
|
def _request_workflow_launch_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:tool_launch_all_workflows',
|
viewname='document_states:tool_launch_workflows',
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_tool_launch_all_workflows_view_no_permission(self):
|
def test_tool_launch_workflows_view_no_permission(self):
|
||||||
self._create_test_workflow(add_document_type=True)
|
self._create_test_workflow(add_document_type=True)
|
||||||
self._create_test_workflow_states()
|
self._create_test_workflow_states()
|
||||||
self._create_test_workflow_transition()
|
self._create_test_workflow_transition()
|
||||||
@@ -129,7 +129,7 @@ class WorkflowToolViewTestCase(WorkflowTestMixin, GenericDocumentViewTestCase):
|
|||||||
|
|
||||||
self.assertEqual(self.test_document.workflows.count(), 0)
|
self.assertEqual(self.test_document.workflows.count(), 0)
|
||||||
|
|
||||||
def test_tool_launch_all_workflows_view_with_permission(self):
|
def test_tool_launch_workflows_view_with_permission(self):
|
||||||
self._create_test_workflow(add_document_type=True)
|
self._create_test_workflow(add_document_type=True)
|
||||||
self._create_test_workflow_states()
|
self._create_test_workflow_states()
|
||||||
self._create_test_workflow_transition()
|
self._create_test_workflow_transition()
|
||||||
|
|||||||
@@ -3,222 +3,247 @@ from __future__ import unicode_literals
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from .api_views import (
|
from .api_views import (
|
||||||
APIDocumentTypeWorkflowListView, APIWorkflowDocumentTypeList,
|
APIDocumentTypeWorkflowRuntimeProxyListView, APIWorkflowDocumentTypeList,
|
||||||
APIWorkflowDocumentTypeView, APIWorkflowImageView,
|
APIWorkflowDocumentTypeView, APIWorkflowImageView,
|
||||||
APIWorkflowInstanceListView, APIWorkflowInstanceView,
|
APIWorkflowInstanceListView, APIWorkflowInstanceView,
|
||||||
APIWorkflowInstanceLogEntryListView, APIWorkflowListView,
|
APIWorkflowInstanceLogEntryListView, APIWorkflowRuntimeProxyListView,
|
||||||
APIWorkflowStateListView, APIWorkflowStateView,
|
APIWorkflowStateListView, APIWorkflowStateView,
|
||||||
APIWorkflowTransitionListView, APIWorkflowTransitionView, APIWorkflowView
|
APIWorkflowTransitionListView, APIWorkflowTransitionView, APIWorkflowView
|
||||||
)
|
)
|
||||||
from .views import (
|
from .views.workflow_instance_views import (
|
||||||
DocumentWorkflowInstanceListView, SetupWorkflowCreateView,
|
WorkflowInstanceDetailView, WorkflowInstanceListView,
|
||||||
SetupWorkflowDeleteView, SetupWorkflowDocumentTypesView,
|
WorkflowInstanceTransitionSelectView,
|
||||||
SetupWorkflowEditView, SetupWorkflowListView,
|
WorkflowInstanceTransitionExecuteView
|
||||||
SetupWorkflowStateActionCreateView, SetupWorkflowStateActionDeleteView,
|
|
||||||
SetupWorkflowStateActionEditView, SetupWorkflowStateActionListView,
|
|
||||||
SetupWorkflowStateActionSelectionView, SetupWorkflowStateCreateView,
|
|
||||||
SetupWorkflowStateDeleteView, SetupWorkflowStateEditView,
|
|
||||||
SetupWorkflowStateListView, SetupWorkflowTransitionListView,
|
|
||||||
SetupWorkflowTransitionCreateView, SetupWorkflowTransitionDeleteView,
|
|
||||||
SetupWorkflowTransitionEditView,
|
|
||||||
SetupWorkflowTransitionTriggerEventListView, ToolLaunchAllWorkflows,
|
|
||||||
WorkflowDocumentListView, WorkflowInstanceDetailView,
|
|
||||||
WorkflowInstanceTransitionExecuteView, WorkflowInstanceTransitionSelectView,
|
|
||||||
WorkflowListView, WorkflowPreviewView, WorkflowStateDocumentListView,
|
|
||||||
WorkflowStateListView,
|
|
||||||
)
|
)
|
||||||
from .views.workflow_views import (
|
from .views.workflow_proxy_views import (
|
||||||
SetupDocumentTypeWorkflowsView, SetupWorkflowTransitionFieldCreateView,
|
WorkflowRuntimeProxyDocumentListView,
|
||||||
SetupWorkflowTransitionFieldDeleteView,
|
WorkflowRuntimeProxyListView, WorkflowRuntimeProxyStateDocumentListView,
|
||||||
SetupWorkflowTransitionFieldEditView, SetupWorkflowTransitionFieldListView
|
WorkflowRuntimeProxyStateListView
|
||||||
|
)
|
||||||
|
from .views.workflow_template_views import (
|
||||||
|
DocumentTypeWorkflowTemplatesView, ToolLaunchWorkflows,
|
||||||
|
WorkflowTemplateCreateView, WorkflowTemplateDeleteView,
|
||||||
|
WorkflowTemplateEditView, WorkflowTemplateListView,
|
||||||
|
WorkflowTemplatePreviewView, WorkflowTemplateDocumentTypesView
|
||||||
|
)
|
||||||
|
from .views.workflow_template_state_views import (
|
||||||
|
WorkflowTemplateStateActionCreateView,
|
||||||
|
WorkflowTemplateStateActionDeleteView, WorkflowTemplateStateActionEditView,
|
||||||
|
WorkflowTemplateStateActionListView,
|
||||||
|
WorkflowTemplateStateActionSelectionView, WorkflowTemplateStateCreateView,
|
||||||
|
WorkflowTemplateStateDeleteView, WorkflowTemplateStateEditView,
|
||||||
|
WorkflowTemplateStateListView
|
||||||
|
)
|
||||||
|
from .views.workflow_template_transition_views import (
|
||||||
|
WorkflowTemplateTransitionCreateView, WorkflowTemplateTransitionDeleteView,
|
||||||
|
WorkflowTemplateTransitionEditView, WorkflowTemplateTransitionListView,
|
||||||
|
WorkflowTemplateTransitionTriggerEventListView,
|
||||||
|
WorkflowTemplateTransitionFieldCreateView,
|
||||||
|
WorkflowTemplateTransitionFieldDeleteView,
|
||||||
|
WorkflowTemplateTransitionFieldEditView,
|
||||||
|
WorkflowTemplateTransitionFieldListView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns_workflows = [
|
urlpatterns_workflow_instances = [
|
||||||
url(
|
url(
|
||||||
regex=r'^document_type/(?P<pk>\d+)/workflows/$',
|
regex=r'^documents/(?P<pk>\d+)/workflows/$',
|
||||||
view=SetupDocumentTypeWorkflowsView.as_view(),
|
view=WorkflowInstanceListView.as_view(),
|
||||||
name='document_type_workflows'
|
name='workflow_instance_list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/workflows/(?P<pk>\d+)/$',
|
||||||
|
view=WorkflowInstanceDetailView.as_view(),
|
||||||
|
name='workflow_instance_detail'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/workflows/(?P<pk>\d+)/transitions/select/$',
|
||||||
|
view=WorkflowInstanceTransitionSelectView.as_view(),
|
||||||
|
name='workflow_instance_transition_selection'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/workflows/(?P<workflow_instance_pk>\d+)/transitions/(?P<workflow_transition_pk>\d+)/execute/$',
|
||||||
|
view=WorkflowInstanceTransitionExecuteView.as_view(),
|
||||||
|
name='workflow_instance_transition_execute'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns_workflow_runtime_proxies = [
|
||||||
|
url(
|
||||||
|
regex=r'workflow_runtime_proxies/$',
|
||||||
|
view=WorkflowRuntimeProxyListView.as_view(),
|
||||||
|
name='workflow_runtime_proxy_list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_runtime_proxies/(?P<pk>\d+)/documents/$',
|
||||||
|
view=WorkflowRuntimeProxyDocumentListView.as_view(),
|
||||||
|
name='workflow_runtime_proxy_document_list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_runtime_proxies/(?P<pk>\d+)/states/$',
|
||||||
|
view=WorkflowRuntimeProxyStateListView.as_view(),
|
||||||
|
name='workflow_runtime_proxy_state_list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_runtime_proxies/states/(?P<pk>\d+)/documents/$',
|
||||||
|
view=WorkflowRuntimeProxyStateDocumentListView.as_view(),
|
||||||
|
name='workflow_runtime_proxy_state_document_list'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns_workflow_states = [
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/(?P<pk>\d+)/states/$',
|
||||||
|
view=WorkflowTemplateStateListView.as_view(),
|
||||||
|
name='workflow_template_state_list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/(?P<pk>\d+)/states/create/$',
|
||||||
|
view=WorkflowTemplateStateCreateView.as_view(),
|
||||||
|
name='workflow_template_state_create'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/states/(?P<pk>\d+)/delete/$',
|
||||||
|
view=WorkflowTemplateStateDeleteView.as_view(),
|
||||||
|
name='workflow_template_state_delete'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/states/(?P<pk>\d+)/edit/$',
|
||||||
|
view=WorkflowTemplateStateEditView.as_view(),
|
||||||
|
name='workflow_template_state_edit'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns_workflow_state_actions = [
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/states/(?P<pk>\d+)/actions/$',
|
||||||
|
view=WorkflowTemplateStateActionListView.as_view(),
|
||||||
|
name='workflow_template_state_action_list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/states/(?P<pk>\d+)/actions/selection/$',
|
||||||
|
view=WorkflowTemplateStateActionSelectionView.as_view(),
|
||||||
|
name='workflow_template_state_action_selection'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/states/(?P<pk>\d+)/actions/(?P<class_path>[a-zA-Z0-9_.]+)/create/$',
|
||||||
|
view=WorkflowTemplateStateActionCreateView.as_view(),
|
||||||
|
name='workflow_template_state_action_create'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/states/actions/(?P<pk>\d+)/delete/$',
|
||||||
|
view=WorkflowTemplateStateActionDeleteView.as_view(),
|
||||||
|
name='workflow_template_state_action_delete'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/states/actions/(?P<pk>\d+)/edit/$',
|
||||||
|
view=WorkflowTemplateStateActionEditView.as_view(),
|
||||||
|
name='workflow_template_state_action_edit'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns_workflow_templates = [
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/$', view=WorkflowTemplateListView.as_view(),
|
||||||
|
name='workflow_template_list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/create/$', view=WorkflowTemplateCreateView.as_view(),
|
||||||
|
name='workflow_template_create'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/(?P<pk>\d+)/delete/$',
|
||||||
|
view=WorkflowTemplateDeleteView.as_view(), name='workflow_template_delete'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/(?P<pk>\d+)/document_types/$',
|
||||||
|
view=WorkflowTemplateDocumentTypesView.as_view(),
|
||||||
|
name='workflow_template_document_types'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/(?P<pk>\d+)/edit/$',
|
||||||
|
view=WorkflowTemplateEditView.as_view(), name='workflow_template_edit'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/(?P<pk>\d+)/preview/$',
|
||||||
|
view=WorkflowTemplatePreviewView.as_view(),
|
||||||
|
name='workflow_template_preview'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^document_types/(?P<pk>\d+)/workflow_templates/$',
|
||||||
|
view=DocumentTypeWorkflowTemplatesView.as_view(),
|
||||||
|
name='document_type_workflow_templates'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
urlpatterns_workflow_transitions = [
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/(?P<pk>\d+)/transitions/$',
|
||||||
|
view=WorkflowTemplateTransitionListView.as_view(),
|
||||||
|
name='workflow_template_transition_list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/(?P<pk>\d+)/transitions/create/$',
|
||||||
|
view=WorkflowTemplateTransitionCreateView.as_view(),
|
||||||
|
name='workflow_template_transition_create'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/(?P<pk>\d+)/transitions/events/$',
|
||||||
|
view=WorkflowTemplateTransitionTriggerEventListView.as_view(),
|
||||||
|
name='workflow_template_transition_events'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/transitions/(?P<pk>\d+)/delete/$',
|
||||||
|
view=WorkflowTemplateTransitionDeleteView.as_view(),
|
||||||
|
name='workflow_template_transition_delete'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^workflow_templates/transitions/(?P<pk>\d+)/edit/$',
|
||||||
|
view=WorkflowTemplateTransitionEditView.as_view(),
|
||||||
|
name='workflow_template_transition_edit'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns_workflow_transition_fields = [
|
urlpatterns_workflow_transition_fields = [
|
||||||
url(
|
url(
|
||||||
regex=r'^setup/workflows/transitions/(?P<pk>\d+)/fields/create/$',
|
regex=r'^workflow_templates/transitions/(?P<pk>\d+)/fields/create/$',
|
||||||
view=SetupWorkflowTransitionFieldCreateView.as_view(),
|
view=WorkflowTemplateTransitionFieldCreateView.as_view(),
|
||||||
name='setup_workflow_transition_field_create'
|
name='workflow_template_transition_field_create'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^setup/workflows/transitions/(?P<pk>\d+)/fields/$',
|
regex=r'^workflow_templates/transitions/(?P<pk>\d+)/fields/$',
|
||||||
view=SetupWorkflowTransitionFieldListView.as_view(),
|
view=WorkflowTemplateTransitionFieldListView.as_view(),
|
||||||
name='setup_workflow_transition_field_list'
|
name='workflow_template_transition_field_list'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^setup/workflows/transitions/fields/(?P<pk>\d+)/delete/$',
|
regex=r'^workflow_templates/transitions/fields/(?P<pk>\d+)/delete/$',
|
||||||
view=SetupWorkflowTransitionFieldDeleteView.as_view(),
|
view=WorkflowTemplateTransitionFieldDeleteView.as_view(),
|
||||||
name='setup_workflow_transition_field_delete'
|
name='workflow_template_transition_field_delete'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^setup/workflows/transitions/fields/(?P<pk>\d+)/edit/$',
|
regex=r'^workflow_templates/transitions/fields/(?P<pk>\d+)/edit/$',
|
||||||
view=SetupWorkflowTransitionFieldEditView.as_view(),
|
view=WorkflowTemplateTransitionFieldEditView.as_view(),
|
||||||
name='setup_workflow_transition_field_edit'
|
name='workflow_template_transition_field_edit'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(
|
url(
|
||||||
regex=r'^document/(?P<pk>\d+)/workflows/$',
|
regex=r'^tools/workflows/launch/$',
|
||||||
view=DocumentWorkflowInstanceListView.as_view(),
|
view=ToolLaunchWorkflows.as_view(),
|
||||||
name='document_workflow_instance_list'
|
name='tool_launch_workflows'
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^document/workflows/(?P<pk>\d+)/$',
|
|
||||||
view=WorkflowInstanceDetailView.as_view(),
|
|
||||||
name='workflow_instance_detail'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^document/workflows/(?P<pk>\d+)/transitions/select/$',
|
|
||||||
view=WorkflowInstanceTransitionSelectView.as_view(),
|
|
||||||
name='workflow_instance_transition_selection'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^document/workflows/(?P<workflow_instance_pk>\d+)/transitions/(?P<workflow_transition_pk>\d+)/execute/$',
|
|
||||||
view=WorkflowInstanceTransitionExecuteView.as_view(),
|
|
||||||
name='workflow_instance_transition_execute'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/all/$', view=SetupWorkflowListView.as_view(),
|
|
||||||
name='setup_workflow_list'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/create/$', view=SetupWorkflowCreateView.as_view(),
|
|
||||||
name='setup_workflow_create'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/(?P<pk>\d+)/edit/$',
|
|
||||||
view=SetupWorkflowEditView.as_view(), name='setup_workflow_edit'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/(?P<pk>\d+)/delete/$',
|
|
||||||
view=SetupWorkflowDeleteView.as_view(), name='setup_workflow_delete'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/(?P<pk>\d+)/documents/$',
|
|
||||||
view=WorkflowDocumentListView.as_view(),
|
|
||||||
name='setup_workflow_document_list'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/(?P<pk>\d+)/document_types/$',
|
|
||||||
view=SetupWorkflowDocumentTypesView.as_view(),
|
|
||||||
name='setup_workflow_document_types'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/(?P<pk>\d+)/states/$',
|
|
||||||
view=SetupWorkflowStateListView.as_view(),
|
|
||||||
name='setup_workflow_state_list'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/(?P<pk>\d+)/states/create/$',
|
|
||||||
view=SetupWorkflowStateCreateView.as_view(),
|
|
||||||
name='setup_workflow_state_create'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/(?P<pk>\d+)/transitions/$',
|
|
||||||
view=SetupWorkflowTransitionListView.as_view(),
|
|
||||||
name='setup_workflow_transition_list'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/(?P<pk>\d+)/transitions/create/$',
|
|
||||||
view=SetupWorkflowTransitionCreateView.as_view(),
|
|
||||||
name='setup_workflow_transition_create'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/(?P<pk>\d+)/transitions/events/$',
|
|
||||||
view=SetupWorkflowTransitionTriggerEventListView.as_view(),
|
|
||||||
name='setup_workflow_transition_events'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/state/(?P<pk>\d+)/delete/$',
|
|
||||||
view=SetupWorkflowStateDeleteView.as_view(),
|
|
||||||
name='setup_workflow_state_delete'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/state/(?P<pk>\d+)/edit/$',
|
|
||||||
view=SetupWorkflowStateEditView.as_view(),
|
|
||||||
name='setup_workflow_state_edit'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/state/(?P<pk>\d+)/actions/$',
|
|
||||||
view=SetupWorkflowStateActionListView.as_view(),
|
|
||||||
name='setup_workflow_state_action_list'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/state/(?P<pk>\d+)/actions/selection/$',
|
|
||||||
view=SetupWorkflowStateActionSelectionView.as_view(),
|
|
||||||
name='setup_workflow_state_action_selection'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/state/(?P<pk>\d+)/actions/(?P<class_path>[a-zA-Z0-9_.]+)/create/$',
|
|
||||||
view=SetupWorkflowStateActionCreateView.as_view(),
|
|
||||||
name='setup_workflow_state_action_create'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/state/actions/(?P<pk>\d+)/delete/$',
|
|
||||||
view=SetupWorkflowStateActionDeleteView.as_view(),
|
|
||||||
name='setup_workflow_state_action_delete'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/state/actions/(?P<pk>\d+)/edit/$',
|
|
||||||
view=SetupWorkflowStateActionEditView.as_view(),
|
|
||||||
name='setup_workflow_state_action_edit'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/transitions/(?P<pk>\d+)/delete/$',
|
|
||||||
view=SetupWorkflowTransitionDeleteView.as_view(),
|
|
||||||
name='setup_workflow_transition_delete'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^setup/workflow/transitions/(?P<pk>\d+)/edit/$',
|
|
||||||
view=SetupWorkflowTransitionEditView.as_view(),
|
|
||||||
name='setup_workflow_transition_edit'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^tools/workflow/all/launch/$',
|
|
||||||
view=ToolLaunchAllWorkflows.as_view(),
|
|
||||||
name='tool_launch_all_workflows'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'all/$',
|
|
||||||
view=WorkflowListView.as_view(),
|
|
||||||
name='workflow_list'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^(?P<pk>\d+)/documents/$',
|
|
||||||
view=WorkflowDocumentListView.as_view(),
|
|
||||||
name='workflow_document_list'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^(?P<pk>\d+)/states/$',
|
|
||||||
view=WorkflowStateListView.as_view(),
|
|
||||||
name='workflow_state_list'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^(?P<pk>\d+)/preview/$',
|
|
||||||
view=WorkflowPreviewView.as_view(),
|
|
||||||
name='workflow_preview'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
regex=r'^state/(?P<pk>\d+)/documents/$',
|
|
||||||
view=WorkflowStateDocumentListView.as_view(),
|
|
||||||
name='workflow_state_document_list'
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns.extend(urlpatterns_workflows)
|
urlpatterns.extend(urlpatterns_workflow_instances)
|
||||||
|
urlpatterns.extend(urlpatterns_workflow_runtime_proxies)
|
||||||
|
urlpatterns.extend(urlpatterns_workflow_states)
|
||||||
|
urlpatterns.extend(urlpatterns_workflow_state_actions)
|
||||||
|
urlpatterns.extend(urlpatterns_workflow_templates)
|
||||||
|
urlpatterns.extend(urlpatterns_workflow_transitions)
|
||||||
urlpatterns.extend(urlpatterns_workflow_transition_fields)
|
urlpatterns.extend(urlpatterns_workflow_transition_fields)
|
||||||
|
|
||||||
api_urls = [
|
api_urls = [
|
||||||
url(
|
url(
|
||||||
regex=r'^workflows/$', view=APIWorkflowListView.as_view(),
|
regex=r'^workflows/$', view=APIWorkflowRuntimeProxyListView.as_view(),
|
||||||
name='workflow-list'
|
name='workflow-list'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
@@ -274,7 +299,7 @@ api_urls = [
|
|||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^document_types/(?P<pk>[0-9]+)/workflows/$',
|
regex=r'^document_types/(?P<pk>[0-9]+)/workflows/$',
|
||||||
view=APIDocumentTypeWorkflowListView.as_view(),
|
view=APIDocumentTypeWorkflowRuntimeProxyListView.as_view(),
|
||||||
name='documenttype-workflow-list'
|
name='documenttype-workflow-list'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,3 +1 @@
|
|||||||
from .workflow_instance_views import * # NOQA
|
|
||||||
from .workflow_proxy_views import * # NOQA
|
|
||||||
from .workflow_views import * # NOQA
|
|
||||||
|
|||||||
@@ -14,20 +14,14 @@ from mayan.apps.common.mixins import ExternalObjectMixin
|
|||||||
from mayan.apps.documents.models import Document
|
from mayan.apps.documents.models import Document
|
||||||
|
|
||||||
from ..forms import WorkflowInstanceTransitionSelectForm
|
from ..forms import WorkflowInstanceTransitionSelectForm
|
||||||
from ..icons import icon_workflow_instance_detail, icon_workflow_list
|
from ..icons import icon_workflow_instance_detail, icon_workflow_template_list
|
||||||
from ..links import link_workflow_instance_transition
|
from ..links import link_workflow_instance_transition
|
||||||
from ..literals import FIELD_TYPE_MAPPING, WIDGET_CLASS_MAPPING
|
from ..literals import FIELD_TYPE_MAPPING, WIDGET_CLASS_MAPPING
|
||||||
from ..models import WorkflowInstance
|
from ..models import WorkflowInstance
|
||||||
from ..permissions import permission_workflow_view
|
from ..permissions import permission_workflow_view
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
'DocumentWorkflowInstanceListView', 'WorkflowInstanceDetailView',
|
|
||||||
'WorkflowInstanceTransitionSelectView',
|
|
||||||
'WorkflowInstanceTransitionExecuteView'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
class WorkflowInstanceListView(SingleObjectListView):
|
||||||
class DocumentWorkflowInstanceListView(SingleObjectListView):
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
AccessControlList.objects.check_access(
|
AccessControlList.objects.check_access(
|
||||||
obj=self.get_document(), permissions=(permission_workflow_view,),
|
obj=self.get_document(), permissions=(permission_workflow_view,),
|
||||||
@@ -35,7 +29,7 @@ class DocumentWorkflowInstanceListView(SingleObjectListView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return super(
|
return super(
|
||||||
DocumentWorkflowInstanceListView, self
|
WorkflowInstanceListView, self
|
||||||
).dispatch(request, *args, **kwargs)
|
).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_document(self):
|
def get_document(self):
|
||||||
@@ -44,7 +38,7 @@ class DocumentWorkflowInstanceListView(SingleObjectListView):
|
|||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
'hide_link': True,
|
'hide_link': True,
|
||||||
'no_results_icon': icon_workflow_list,
|
'no_results_icon': icon_workflow_template_list,
|
||||||
'no_results_text': _(
|
'no_results_text': _(
|
||||||
'Assign workflows to the document type of this document '
|
'Assign workflows to the document type of this document '
|
||||||
'to have this document execute those workflows. '
|
'to have this document execute those workflows. '
|
||||||
|
|||||||
@@ -9,18 +9,13 @@ from mayan.apps.common.generics import SingleObjectListView
|
|||||||
from mayan.apps.documents.models import Document
|
from mayan.apps.documents.models import Document
|
||||||
from mayan.apps.documents.views import DocumentListView
|
from mayan.apps.documents.views import DocumentListView
|
||||||
|
|
||||||
from ..icons import icon_workflow_list
|
from ..icons import icon_workflow_template_list
|
||||||
from ..links import link_setup_workflow_create, link_setup_workflow_state_create
|
from ..links import link_workflow_template_create, link_workflow_template_state_create
|
||||||
from ..models import WorkflowRuntimeProxy, WorkflowStateRuntimeProxy
|
from ..models import WorkflowRuntimeProxy, WorkflowStateRuntimeProxy
|
||||||
from ..permissions import permission_workflow_view
|
from ..permissions import permission_workflow_view
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
'WorkflowDocumentListView', 'WorkflowListView',
|
|
||||||
'WorkflowStateDocumentListView', 'WorkflowStateListView'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
class WorkflowRuntimeProxyDocumentListView(DocumentListView):
|
||||||
class WorkflowDocumentListView(DocumentListView):
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.workflow = get_object_or_404(
|
self.workflow = get_object_or_404(
|
||||||
klass=WorkflowRuntimeProxy, pk=self.kwargs['pk']
|
klass=WorkflowRuntimeProxy, pk=self.kwargs['pk']
|
||||||
@@ -32,14 +27,14 @@ class WorkflowDocumentListView(DocumentListView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return super(
|
return super(
|
||||||
WorkflowDocumentListView, self
|
WorkflowRuntimeProxyDocumentListView, self
|
||||||
).dispatch(request, *args, **kwargs)
|
).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_document_queryset(self):
|
def get_document_queryset(self):
|
||||||
return Document.objects.filter(workflows__workflow=self.workflow)
|
return Document.objects.filter(workflows__workflow=self.workflow)
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
context = super(WorkflowDocumentListView, self).get_extra_context()
|
context = super(WorkflowRuntimeProxyDocumentListView, self).get_extra_context()
|
||||||
context.update(
|
context.update(
|
||||||
{
|
{
|
||||||
'no_results_text': _(
|
'no_results_text': _(
|
||||||
@@ -56,14 +51,14 @@ class WorkflowDocumentListView(DocumentListView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class WorkflowListView(SingleObjectListView):
|
class WorkflowRuntimeProxyListView(SingleObjectListView):
|
||||||
object_permission = permission_workflow_view
|
object_permission = permission_workflow_view
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
'hide_object': True,
|
'hide_object': True,
|
||||||
'no_results_icon': icon_workflow_list,
|
'no_results_icon': icon_workflow_template_list,
|
||||||
'no_results_main_link': link_setup_workflow_create.resolve(
|
'no_results_main_link': link_workflow_template_create.resolve(
|
||||||
context=RequestContext(request=self.request)
|
context=RequestContext(request=self.request)
|
||||||
),
|
),
|
||||||
'no_results_text': _(
|
'no_results_text': _(
|
||||||
@@ -79,13 +74,13 @@ class WorkflowListView(SingleObjectListView):
|
|||||||
return WorkflowRuntimeProxy.objects.all()
|
return WorkflowRuntimeProxy.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class WorkflowStateDocumentListView(DocumentListView):
|
class WorkflowRuntimeProxyStateDocumentListView(DocumentListView):
|
||||||
def get_document_queryset(self):
|
def get_document_queryset(self):
|
||||||
return self.get_workflow_state().get_documents()
|
return self.get_workflow_state().get_documents()
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
workflow_state = self.get_workflow_state()
|
workflow_state = self.get_workflow_state()
|
||||||
context = super(WorkflowStateDocumentListView, self).get_extra_context()
|
context = super(WorkflowRuntimeProxyStateDocumentListView, self).get_extra_context()
|
||||||
context.update(
|
context.update(
|
||||||
{
|
{
|
||||||
'object': workflow_state,
|
'object': workflow_state,
|
||||||
@@ -118,7 +113,7 @@ class WorkflowStateDocumentListView(DocumentListView):
|
|||||||
return workflow_state
|
return workflow_state
|
||||||
|
|
||||||
|
|
||||||
class WorkflowStateListView(SingleObjectListView):
|
class WorkflowRuntimeProxyStateListView(SingleObjectListView):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
AccessControlList.objects.check_access(
|
AccessControlList.objects.check_access(
|
||||||
obj=self.get_workflow(), permissions=(permission_workflow_view,),
|
obj=self.get_workflow(), permissions=(permission_workflow_view,),
|
||||||
@@ -126,14 +121,14 @@ class WorkflowStateListView(SingleObjectListView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return super(
|
return super(
|
||||||
WorkflowStateListView, self
|
WorkflowRuntimeProxyStateListView, self
|
||||||
).dispatch(request, *args, **kwargs)
|
).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
'hide_link': True,
|
'hide_link': True,
|
||||||
'hide_object': True,
|
'hide_object': True,
|
||||||
'no_results_main_link': link_setup_workflow_state_create.resolve(
|
'no_results_main_link': link_workflow_template_state_create.resolve(
|
||||||
context=RequestContext(
|
context=RequestContext(
|
||||||
request=self.request, dict_={'object': self.get_workflow()}
|
request=self.request, dict_={'object': self.get_workflow()}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,326 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.db import transaction
|
||||||
|
from django.http import Http404, HttpResponseRedirect
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.template import RequestContext
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.common.generics import (
|
||||||
|
AddRemoveView, ConfirmView, FormView, SingleObjectCreateView,
|
||||||
|
SingleObjectDeleteView, SingleObjectDetailView,
|
||||||
|
SingleObjectDynamicFormCreateView, SingleObjectDynamicFormEditView,
|
||||||
|
SingleObjectEditView, SingleObjectListView
|
||||||
|
)
|
||||||
|
from mayan.apps.common.mixins import ExternalObjectMixin
|
||||||
|
from mayan.apps.documents.events import event_document_type_edited
|
||||||
|
from mayan.apps.documents.models import DocumentType
|
||||||
|
from mayan.apps.documents.permissions import permission_document_type_edit
|
||||||
|
from mayan.apps.events.classes import EventType
|
||||||
|
from mayan.apps.events.models import StoredEventType
|
||||||
|
|
||||||
|
from ..classes import WorkflowAction
|
||||||
|
from ..events import event_workflow_edited
|
||||||
|
from ..forms import (
|
||||||
|
WorkflowActionSelectionForm, WorkflowForm, WorkflowPreviewForm,
|
||||||
|
WorkflowStateActionDynamicForm, WorkflowStateForm, WorkflowTransitionForm,
|
||||||
|
WorkflowTransitionTriggerEventRelationshipFormSet
|
||||||
|
)
|
||||||
|
from ..icons import (
|
||||||
|
icon_workflow_template_list, icon_workflow_state, icon_workflow_state_action,
|
||||||
|
icon_workflow_transition, icon_workflow_transition_field
|
||||||
|
)
|
||||||
|
from ..links import (
|
||||||
|
link_workflow_template_create, link_workflow_template_state_create,
|
||||||
|
link_workflow_template_state_action_selection,
|
||||||
|
link_workflow_template_transition_create,
|
||||||
|
link_workflow_template_transition_field_create,
|
||||||
|
)
|
||||||
|
from ..models import (
|
||||||
|
Workflow, WorkflowState, WorkflowStateAction, WorkflowTransition,
|
||||||
|
WorkflowTransitionField
|
||||||
|
)
|
||||||
|
from ..permissions import (
|
||||||
|
permission_workflow_create, permission_workflow_delete,
|
||||||
|
permission_workflow_edit, permission_workflow_tools,
|
||||||
|
permission_workflow_view,
|
||||||
|
)
|
||||||
|
from ..tasks import task_launch_all_workflows
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateStateActionCreateView(SingleObjectDynamicFormCreateView):
|
||||||
|
form_class = WorkflowStateActionDynamicForm
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
|
||||||
|
def get_class(self):
|
||||||
|
try:
|
||||||
|
return WorkflowAction.get(name=self.kwargs['class_path'])
|
||||||
|
except KeyError:
|
||||||
|
raise Http404(
|
||||||
|
'{} class not found'.format(self.kwargs['class_path'])
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'navigation_object_list': ('object', 'workflow'),
|
||||||
|
'object': self.get_object(),
|
||||||
|
'title': _(
|
||||||
|
'Create a "%s" workflow action'
|
||||||
|
) % self.get_class().label,
|
||||||
|
'workflow': self.get_object().workflow
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_extra_kwargs(self):
|
||||||
|
return {
|
||||||
|
'request': self.request,
|
||||||
|
'action_path': self.kwargs['class_path']
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_schema(self):
|
||||||
|
return self.get_class()().get_form_schema(request=self.request)
|
||||||
|
|
||||||
|
def get_instance_extra_data(self):
|
||||||
|
return {
|
||||||
|
'action_path': self.kwargs['class_path'],
|
||||||
|
'state': self.get_object()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return get_object_or_404(klass=WorkflowState, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
def get_post_action_redirect(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_state_action_list',
|
||||||
|
kwargs={'pk': self.get_object().pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateStateActionDeleteView(SingleObjectDeleteView):
|
||||||
|
model = WorkflowStateAction
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'navigation_object_list': (
|
||||||
|
'object', 'workflow_state', 'workflow'
|
||||||
|
),
|
||||||
|
'object': self.get_object(),
|
||||||
|
'title': _('Delete workflow state action: %s') % self.get_object(),
|
||||||
|
'workflow': self.get_object().state.workflow,
|
||||||
|
'workflow_state': self.get_object().state,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_post_action_redirect(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_state_action_list',
|
||||||
|
kwargs={'pk': self.get_object().state.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateStateActionEditView(SingleObjectDynamicFormEditView):
|
||||||
|
form_class = WorkflowStateActionDynamicForm
|
||||||
|
model = WorkflowStateAction
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'navigation_object_list': (
|
||||||
|
'object', 'workflow_state', 'workflow'
|
||||||
|
),
|
||||||
|
'object': self.get_object(),
|
||||||
|
'title': _('Edit workflow state action: %s') % self.get_object(),
|
||||||
|
'workflow': self.get_object().state.workflow,
|
||||||
|
'workflow_state': self.get_object().state,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_extra_kwargs(self):
|
||||||
|
return {
|
||||||
|
'request': self.request,
|
||||||
|
'action_path': self.get_object().action_path,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_schema(self):
|
||||||
|
return self.get_object().get_class_instance().get_form_schema(
|
||||||
|
request=self.request
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_post_action_redirect(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_state_action_list',
|
||||||
|
kwargs={'pk': self.get_object().state.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateStateActionListView(SingleObjectListView):
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_object': True,
|
||||||
|
'navigation_object_list': ('object', 'workflow'),
|
||||||
|
'no_results_icon': icon_workflow_state_action,
|
||||||
|
'no_results_main_link': link_workflow_template_state_action_selection.resolve(
|
||||||
|
context=RequestContext(
|
||||||
|
request=self.request, dict_={
|
||||||
|
'object': self.get_workflow_state()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'no_results_text': _(
|
||||||
|
'Workflow state actions are macros that get executed when '
|
||||||
|
'documents enters or leaves the state in which they reside.'
|
||||||
|
),
|
||||||
|
'no_results_title': _(
|
||||||
|
'There are no actions for this workflow state'
|
||||||
|
),
|
||||||
|
'object': self.get_workflow_state(),
|
||||||
|
'title': _(
|
||||||
|
'Actions for workflow state: %s'
|
||||||
|
) % self.get_workflow_state(),
|
||||||
|
'workflow': self.get_workflow_state().workflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_schema(self):
|
||||||
|
return {'fields': self.get_class().fields}
|
||||||
|
|
||||||
|
def get_source_queryset(self):
|
||||||
|
return self.get_workflow_state().actions.all()
|
||||||
|
|
||||||
|
def get_workflow_state(self):
|
||||||
|
return get_object_or_404(klass=WorkflowState, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateStateActionSelectionView(FormView):
|
||||||
|
form_class = WorkflowActionSelectionForm
|
||||||
|
view_permission = permission_workflow_edit
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
klass = form.cleaned_data['klass']
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
redirect_to=reverse(
|
||||||
|
viewname='document_states:workflow_template_state_action_create',
|
||||||
|
kwargs={'pk': self.get_object().pk, 'class_path': klass}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'navigation_object_list': (
|
||||||
|
'object', 'workflow'
|
||||||
|
),
|
||||||
|
'object': self.get_object(),
|
||||||
|
'title': _('New workflow state action selection'),
|
||||||
|
'workflow': self.get_object().workflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return get_object_or_404(klass=WorkflowState, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateStateCreateView(ExternalObjectMixin, SingleObjectCreateView):
|
||||||
|
external_object_class = Workflow
|
||||||
|
external_object_permission = permission_workflow_edit
|
||||||
|
external_object_pk_url_kwarg = 'pk'
|
||||||
|
form_class = WorkflowStateForm
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'object': self.get_workflow(),
|
||||||
|
'title': _(
|
||||||
|
'Create states for workflow: %s'
|
||||||
|
) % self.get_workflow()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_instance_extra_data(self):
|
||||||
|
return {'workflow': self.get_workflow()}
|
||||||
|
|
||||||
|
def get_source_queryset(self):
|
||||||
|
return self.get_workflow().states.all()
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_state_list',
|
||||||
|
kwargs={'pk': self.kwargs['pk']}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_workflow(self):
|
||||||
|
return self.external_object
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateStateDeleteView(SingleObjectDeleteView):
|
||||||
|
model = WorkflowState
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
pk_url_kwarg = 'pk'
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'navigation_object_list': ('object', 'workflow_instance'),
|
||||||
|
'object': self.get_object(),
|
||||||
|
'title': _(
|
||||||
|
'Delete workflow state: %s?'
|
||||||
|
) % self.object,
|
||||||
|
'workflow_instance': self.get_object().workflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_state_list',
|
||||||
|
kwargs={'pk': self.get_object().workflow.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateStateEditView(SingleObjectEditView):
|
||||||
|
form_class = WorkflowStateForm
|
||||||
|
model = WorkflowState
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
pk_url_kwarg = 'pk'
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'navigation_object_list': ('object', 'workflow_instance'),
|
||||||
|
'object': self.get_object(),
|
||||||
|
'title': _(
|
||||||
|
'Edit workflow state: %s'
|
||||||
|
) % self.object,
|
||||||
|
'workflow_instance': self.get_object().workflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_state_list',
|
||||||
|
kwargs={'pk': self.get_object().workflow.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateStateListView(ExternalObjectMixin, SingleObjectListView):
|
||||||
|
external_object_class = Workflow
|
||||||
|
external_object_permission = permission_workflow_view
|
||||||
|
external_object_pk_url_kwarg = 'pk'
|
||||||
|
object_permission = permission_workflow_view
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_object': True,
|
||||||
|
'no_results_icon': icon_workflow_state,
|
||||||
|
'no_results_main_link': link_workflow_template_state_create.resolve(
|
||||||
|
context=RequestContext(
|
||||||
|
self.request, {'object': self.get_workflow()}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'no_results_text': _(
|
||||||
|
'Create states and link them using transitions.'
|
||||||
|
),
|
||||||
|
'no_results_title': _(
|
||||||
|
'This workflow doesn\'t have any states'
|
||||||
|
),
|
||||||
|
'object': self.get_workflow(),
|
||||||
|
'title': _('States of workflow: %s') % self.get_workflow()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_source_queryset(self):
|
||||||
|
return self.get_workflow().states.all()
|
||||||
|
|
||||||
|
def get_workflow(self):
|
||||||
|
return self.external_object
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.db import transaction
|
||||||
|
from django.http import Http404, HttpResponseRedirect
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.template import RequestContext
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.common.generics import (
|
||||||
|
AddRemoveView, ConfirmView, FormView, SingleObjectCreateView,
|
||||||
|
SingleObjectDeleteView, SingleObjectDetailView,
|
||||||
|
SingleObjectDynamicFormCreateView, SingleObjectDynamicFormEditView,
|
||||||
|
SingleObjectEditView, SingleObjectListView
|
||||||
|
)
|
||||||
|
from mayan.apps.common.mixins import ExternalObjectMixin
|
||||||
|
from mayan.apps.documents.events import event_document_type_edited
|
||||||
|
from mayan.apps.documents.models import DocumentType
|
||||||
|
from mayan.apps.documents.permissions import permission_document_type_edit
|
||||||
|
from mayan.apps.events.classes import EventType
|
||||||
|
from mayan.apps.events.models import StoredEventType
|
||||||
|
|
||||||
|
from ..classes import WorkflowAction
|
||||||
|
from ..events import event_workflow_edited
|
||||||
|
from ..forms import (
|
||||||
|
WorkflowActionSelectionForm, WorkflowForm, WorkflowPreviewForm,
|
||||||
|
WorkflowStateActionDynamicForm, WorkflowStateForm, WorkflowTransitionForm,
|
||||||
|
WorkflowTransitionTriggerEventRelationshipFormSet
|
||||||
|
)
|
||||||
|
from ..icons import (
|
||||||
|
icon_workflow_template_list, icon_workflow_state, icon_workflow_state_action,
|
||||||
|
icon_workflow_transition, icon_workflow_transition_field
|
||||||
|
)
|
||||||
|
from ..links import (
|
||||||
|
link_workflow_template_create, link_workflow_template_state_create,
|
||||||
|
link_workflow_template_state_action_selection,
|
||||||
|
link_workflow_template_transition_create,
|
||||||
|
link_workflow_template_transition_field_create,
|
||||||
|
)
|
||||||
|
from ..models import (
|
||||||
|
Workflow, WorkflowState, WorkflowStateAction, WorkflowTransition,
|
||||||
|
WorkflowTransitionField
|
||||||
|
)
|
||||||
|
from ..permissions import (
|
||||||
|
permission_workflow_create, permission_workflow_delete,
|
||||||
|
permission_workflow_edit, permission_workflow_tools,
|
||||||
|
permission_workflow_view,
|
||||||
|
)
|
||||||
|
from ..tasks import task_launch_all_workflows
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateTransitionCreateView(ExternalObjectMixin, SingleObjectCreateView):
|
||||||
|
external_object_class = Workflow
|
||||||
|
external_object_permission = permission_workflow_edit
|
||||||
|
external_object_pk_url_kwarg = 'pk'
|
||||||
|
form_class = WorkflowTransitionForm
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'object': self.get_workflow(),
|
||||||
|
'title': _(
|
||||||
|
'Create transitions for workflow: %s'
|
||||||
|
) % self.get_workflow()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super(
|
||||||
|
WorkflowTemplateTransitionCreateView, self
|
||||||
|
).get_form_kwargs()
|
||||||
|
kwargs['workflow'] = self.get_workflow()
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_instance_extra_data(self):
|
||||||
|
return {'workflow': self.get_workflow()}
|
||||||
|
|
||||||
|
def get_source_queryset(self):
|
||||||
|
return self.get_workflow().transitions.all()
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_transition_list',
|
||||||
|
kwargs={'pk': self.kwargs['pk']}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_workflow(self):
|
||||||
|
return self.external_object
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateTransitionDeleteView(SingleObjectDeleteView):
|
||||||
|
model = WorkflowTransition
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
pk_url_kwarg = 'pk'
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'object': self.get_object(),
|
||||||
|
'navigation_object_list': ('object', 'workflow_instance'),
|
||||||
|
'title': _(
|
||||||
|
'Delete workflow transition: %s?'
|
||||||
|
) % self.object,
|
||||||
|
'workflow_instance': self.get_object().workflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_transition_list',
|
||||||
|
kwargs={'pk': self.get_object().workflow.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateTransitionEditView(SingleObjectEditView):
|
||||||
|
form_class = WorkflowTransitionForm
|
||||||
|
model = WorkflowTransition
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
pk_url_kwarg = 'pk'
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'navigation_object_list': ('object', 'workflow_instance'),
|
||||||
|
'object': self.get_object(),
|
||||||
|
'title': _(
|
||||||
|
'Edit workflow transition: %s'
|
||||||
|
) % self.object,
|
||||||
|
'workflow_instance': self.get_object().workflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super(
|
||||||
|
WorkflowTemplateTransitionEditView, self
|
||||||
|
).get_form_kwargs()
|
||||||
|
kwargs['workflow'] = self.get_object().workflow
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_transition_list',
|
||||||
|
kwargs={'pk': self.get_object().workflow.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateTransitionListView(ExternalObjectMixin, SingleObjectListView):
|
||||||
|
external_object_class = Workflow
|
||||||
|
external_object_permission = permission_workflow_view
|
||||||
|
external_object_pk_url_kwarg = 'pk'
|
||||||
|
object_permission = permission_workflow_view
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_object': True,
|
||||||
|
'no_results_icon': icon_workflow_transition,
|
||||||
|
'no_results_main_link': link_workflow_template_transition_create.resolve(
|
||||||
|
context=RequestContext(
|
||||||
|
self.request, {'object': self.get_workflow()}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'no_results_text': _(
|
||||||
|
'Create a transition and use it to move a workflow from '
|
||||||
|
' one state to another.'
|
||||||
|
),
|
||||||
|
'no_results_title': _(
|
||||||
|
'This workflow doesn\'t have any transitions'
|
||||||
|
),
|
||||||
|
'object': self.get_workflow(),
|
||||||
|
'title': _(
|
||||||
|
'Transitions of workflow: %s'
|
||||||
|
) % self.get_workflow()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_source_queryset(self):
|
||||||
|
return self.get_workflow().transitions.all()
|
||||||
|
|
||||||
|
def get_workflow(self):
|
||||||
|
return self.external_object
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateTransitionTriggerEventListView(ExternalObjectMixin, FormView):
|
||||||
|
external_object_class = WorkflowTransition
|
||||||
|
external_object_permission = permission_workflow_edit
|
||||||
|
external_object_pk_url_kwarg = 'pk'
|
||||||
|
form_class = WorkflowTransitionTriggerEventRelationshipFormSet
|
||||||
|
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
EventType.refresh()
|
||||||
|
return super(
|
||||||
|
WorkflowTemplateTransitionTriggerEventListView, self
|
||||||
|
).dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
try:
|
||||||
|
for instance in form:
|
||||||
|
instance.save()
|
||||||
|
except Exception as exception:
|
||||||
|
messages.error(
|
||||||
|
message=_(
|
||||||
|
'Error updating workflow transition trigger events; %s'
|
||||||
|
) % exception, request=self.request
|
||||||
|
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
messages.success(
|
||||||
|
message=_(
|
||||||
|
'Workflow transition trigger events updated successfully'
|
||||||
|
), request=self.request
|
||||||
|
)
|
||||||
|
|
||||||
|
return super(
|
||||||
|
WorkflowTemplateTransitionTriggerEventListView, self
|
||||||
|
).form_valid(form=form)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'form_display_mode_table': True,
|
||||||
|
'navigation_object_list': ('object', 'workflow'),
|
||||||
|
'object': self.get_object(),
|
||||||
|
'subtitle': _(
|
||||||
|
'Triggers are events that cause this transition to execute '
|
||||||
|
'automatically.'
|
||||||
|
),
|
||||||
|
'title': _(
|
||||||
|
'Workflow transition trigger events for: %s'
|
||||||
|
) % self.get_object(),
|
||||||
|
'workflow': self.get_object().workflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
obj = self.get_object()
|
||||||
|
initial = []
|
||||||
|
|
||||||
|
# Return the queryset by name from the sorted list of the class
|
||||||
|
event_type_ids = [event_type.id for event_type in EventType.all()]
|
||||||
|
event_type_queryset = StoredEventType.objects.filter(
|
||||||
|
name__in=event_type_ids
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sort queryset in Python by namespace, then by label
|
||||||
|
event_type_queryset = sorted(
|
||||||
|
event_type_queryset, key=lambda x: (x.namespace, x.label)
|
||||||
|
)
|
||||||
|
|
||||||
|
for event_type in event_type_queryset:
|
||||||
|
initial.append({
|
||||||
|
'transition': obj,
|
||||||
|
'event_type': event_type,
|
||||||
|
})
|
||||||
|
return initial
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.external_object
|
||||||
|
|
||||||
|
def get_post_action_redirect(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_transition_list',
|
||||||
|
kwargs={'pk': self.get_object().workflow.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateTransitionFieldCreateView(ExternalObjectMixin, SingleObjectCreateView):
|
||||||
|
external_object_class = WorkflowTransition
|
||||||
|
external_object_permission = permission_workflow_edit
|
||||||
|
fields = (
|
||||||
|
'name', 'label', 'field_type', 'help_text', 'required', 'widget',
|
||||||
|
'widget_kwargs'
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'navigation_object_list': ('transition', 'workflow'),
|
||||||
|
'transition': self.external_object,
|
||||||
|
'title': _(
|
||||||
|
'Create a field for workflow transition: %s'
|
||||||
|
) % self.external_object,
|
||||||
|
'workflow': self.external_object.workflow
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_instance_extra_data(self):
|
||||||
|
return {
|
||||||
|
'transition': self.external_object,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.external_object.fields.all()
|
||||||
|
|
||||||
|
def get_post_action_redirect(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_transition_field_list',
|
||||||
|
kwargs={'pk': self.external_object.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateTransitionFieldDeleteView(SingleObjectDeleteView):
|
||||||
|
model = WorkflowTransitionField
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'navigation_object_list': (
|
||||||
|
'object', 'workflow_transition', 'workflow'
|
||||||
|
),
|
||||||
|
'object': self.object,
|
||||||
|
'title': _('Delete workflow transition field: %s') % self.object,
|
||||||
|
'workflow': self.object.transition.workflow,
|
||||||
|
'workflow_transition': self.object.transition,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_post_action_redirect(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_transition_field_list',
|
||||||
|
kwargs={'pk': self.object.transition.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateTransitionFieldEditView(SingleObjectEditView):
|
||||||
|
fields = (
|
||||||
|
'name', 'label', 'field_type', 'help_text', 'required', 'widget',
|
||||||
|
'widget_kwargs'
|
||||||
|
)
|
||||||
|
model = WorkflowTransitionField
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'navigation_object_list': (
|
||||||
|
'object', 'workflow_transition', 'workflow'
|
||||||
|
),
|
||||||
|
'object': self.object,
|
||||||
|
'title': _('Edit workflow transition field: %s') % self.object,
|
||||||
|
'workflow': self.object.transition.workflow,
|
||||||
|
'workflow_transition': self.object.transition,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_post_action_redirect(self):
|
||||||
|
return reverse(
|
||||||
|
viewname='document_states:workflow_template_transition_field_list',
|
||||||
|
kwargs={'pk': self.object.transition.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateTransitionFieldListView(ExternalObjectMixin, SingleObjectListView):
|
||||||
|
external_object_class = WorkflowTransition
|
||||||
|
external_object_permission = permission_workflow_edit
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_object': True,
|
||||||
|
'navigation_object_list': ('object', 'workflow'),
|
||||||
|
'no_results_icon': icon_workflow_transition_field,
|
||||||
|
'no_results_main_link': link_workflow_template_transition_field_create.resolve(
|
||||||
|
context=RequestContext(
|
||||||
|
request=self.request, dict_={
|
||||||
|
'object': self.external_object
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'no_results_text': _(
|
||||||
|
'Workflow transition fields allow adding data to the '
|
||||||
|
'workflow\'s context. This additional context data can then '
|
||||||
|
'be used by other elements of the workflow system like the '
|
||||||
|
'workflow state actions.'
|
||||||
|
),
|
||||||
|
'no_results_title': _(
|
||||||
|
'There are no fields for this workflow transition'
|
||||||
|
),
|
||||||
|
'object': self.external_object,
|
||||||
|
'title': _(
|
||||||
|
'Fields for workflow transition: %s'
|
||||||
|
) % self.external_object,
|
||||||
|
'workflow': self.external_object.workflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_source_queryset(self):
|
||||||
|
return self.external_object.fields.all()
|
||||||
261
mayan/apps/document_states/views/workflow_template_views.py
Normal file
261
mayan/apps/document_states/views/workflow_template_views.py
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.db import transaction
|
||||||
|
from django.http import Http404, HttpResponseRedirect
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.template import RequestContext
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.common.generics import (
|
||||||
|
AddRemoveView, ConfirmView, FormView, SingleObjectCreateView,
|
||||||
|
SingleObjectDeleteView, SingleObjectDetailView,
|
||||||
|
SingleObjectDynamicFormCreateView, SingleObjectDynamicFormEditView,
|
||||||
|
SingleObjectEditView, SingleObjectListView
|
||||||
|
)
|
||||||
|
from mayan.apps.common.mixins import ExternalObjectMixin
|
||||||
|
from mayan.apps.documents.events import event_document_type_edited
|
||||||
|
from mayan.apps.documents.models import DocumentType
|
||||||
|
from mayan.apps.documents.permissions import permission_document_type_edit
|
||||||
|
from mayan.apps.events.classes import EventType
|
||||||
|
from mayan.apps.events.models import StoredEventType
|
||||||
|
|
||||||
|
from ..classes import WorkflowAction
|
||||||
|
from ..events import event_workflow_edited
|
||||||
|
from ..forms import (
|
||||||
|
WorkflowActionSelectionForm, WorkflowForm, WorkflowPreviewForm,
|
||||||
|
WorkflowStateActionDynamicForm, WorkflowStateForm, WorkflowTransitionForm,
|
||||||
|
WorkflowTransitionTriggerEventRelationshipFormSet
|
||||||
|
)
|
||||||
|
from ..icons import (
|
||||||
|
icon_workflow_template_list, icon_workflow_state, icon_workflow_state_action,
|
||||||
|
icon_workflow_transition, icon_workflow_transition_field
|
||||||
|
)
|
||||||
|
from ..links import (
|
||||||
|
link_workflow_template_create, link_workflow_template_state_create,
|
||||||
|
link_workflow_template_state_action_selection,
|
||||||
|
link_workflow_template_transition_create,
|
||||||
|
link_workflow_template_transition_field_create,
|
||||||
|
)
|
||||||
|
from ..models import (
|
||||||
|
Workflow, WorkflowState, WorkflowStateAction, WorkflowTransition,
|
||||||
|
WorkflowTransitionField
|
||||||
|
)
|
||||||
|
from ..permissions import (
|
||||||
|
permission_workflow_create, permission_workflow_delete,
|
||||||
|
permission_workflow_edit, permission_workflow_tools,
|
||||||
|
permission_workflow_view,
|
||||||
|
)
|
||||||
|
from ..tasks import task_launch_all_workflows
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentTypeWorkflowTemplatesView(AddRemoveView):
|
||||||
|
main_object_permission = permission_document_type_edit
|
||||||
|
main_object_model = DocumentType
|
||||||
|
main_object_pk_url_kwarg = 'pk'
|
||||||
|
secondary_object_model = Workflow
|
||||||
|
secondary_object_permission = permission_workflow_edit
|
||||||
|
list_available_title = _('Available workflows')
|
||||||
|
list_added_title = _('Workflows assigned this document type')
|
||||||
|
related_field = 'workflows'
|
||||||
|
|
||||||
|
def get_actions_extra_kwargs(self):
|
||||||
|
return {'_user': self.request.user}
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'object': self.main_object,
|
||||||
|
'subtitle': _(
|
||||||
|
'Removing a workflow from a document type will also '
|
||||||
|
'remove all running instances of that workflow.'
|
||||||
|
),
|
||||||
|
'title': _(
|
||||||
|
'Workflows assigned the document type: %s'
|
||||||
|
) % self.main_object,
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_add(self, queryset, _user):
|
||||||
|
with transaction.atomic():
|
||||||
|
event_document_type_edited.commit(
|
||||||
|
actor=_user, target=self.main_object
|
||||||
|
)
|
||||||
|
|
||||||
|
for obj in queryset:
|
||||||
|
self.main_object.workflows.add(obj)
|
||||||
|
event_workflow_edited.commit(
|
||||||
|
action_object=self.main_object, actor=_user, target=obj
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_remove(self, queryset, _user):
|
||||||
|
with transaction.atomic():
|
||||||
|
event_document_type_edited.commit(
|
||||||
|
actor=_user, target=self.main_object
|
||||||
|
)
|
||||||
|
|
||||||
|
for obj in queryset:
|
||||||
|
self.main_object.workflows.remove(obj)
|
||||||
|
event_workflow_edited.commit(
|
||||||
|
action_object=self.main_object, actor=_user,
|
||||||
|
target=obj
|
||||||
|
)
|
||||||
|
obj.instances.filter(
|
||||||
|
document__document_type=self.main_object
|
||||||
|
).delete()
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateListView(SingleObjectListView):
|
||||||
|
model = Workflow
|
||||||
|
object_permission = permission_workflow_view
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_object': True,
|
||||||
|
'no_results_icon': icon_workflow_template_list,
|
||||||
|
'no_results_main_link': link_workflow_template_create.resolve(
|
||||||
|
context=RequestContext(request=self.request)
|
||||||
|
),
|
||||||
|
'no_results_text': _(
|
||||||
|
'Workflows store a series of states and keep track of the '
|
||||||
|
'current state of a document. Transitions are used to change the '
|
||||||
|
'current state to a new one.'
|
||||||
|
),
|
||||||
|
'no_results_title': _(
|
||||||
|
'No workflows have been defined'
|
||||||
|
),
|
||||||
|
'title': _('Workflows'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateCreateView(SingleObjectCreateView):
|
||||||
|
extra_context = {'title': _('Create workflow')}
|
||||||
|
form_class = WorkflowForm
|
||||||
|
model = Workflow
|
||||||
|
post_action_redirect = reverse_lazy(
|
||||||
|
viewname='document_states:workflow_template_list'
|
||||||
|
)
|
||||||
|
view_permission = permission_workflow_create
|
||||||
|
|
||||||
|
def get_save_extra_data(self):
|
||||||
|
return {'_user': self.request.user}
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateDeleteView(SingleObjectDeleteView):
|
||||||
|
model = Workflow
|
||||||
|
object_permission = permission_workflow_delete
|
||||||
|
post_action_redirect = reverse_lazy(
|
||||||
|
viewname='document_states:workflow_template_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'title': _(
|
||||||
|
'Delete workflow: %s?'
|
||||||
|
) % self.object,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateEditView(SingleObjectEditView):
|
||||||
|
form_class = WorkflowForm
|
||||||
|
model = Workflow
|
||||||
|
object_permission = permission_workflow_edit
|
||||||
|
post_action_redirect = reverse_lazy(
|
||||||
|
viewname='document_states:workflow_template_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'title': _(
|
||||||
|
'Edit workflow: %s'
|
||||||
|
) % self.object,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_save_extra_data(self):
|
||||||
|
return {'_user': self.request.user}
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplateDocumentTypesView(AddRemoveView):
|
||||||
|
main_object_permission = permission_workflow_edit
|
||||||
|
main_object_model = Workflow
|
||||||
|
main_object_pk_url_kwarg = 'pk'
|
||||||
|
secondary_object_model = DocumentType
|
||||||
|
secondary_object_permission = permission_document_type_edit
|
||||||
|
list_available_title = _('Available document types')
|
||||||
|
list_added_title = _('Document types assigned this workflow')
|
||||||
|
related_field = 'document_types'
|
||||||
|
|
||||||
|
def get_actions_extra_kwargs(self):
|
||||||
|
return {'_user': self.request.user}
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'object': self.main_object,
|
||||||
|
'subtitle': _(
|
||||||
|
'Removing a document type from a workflow will also '
|
||||||
|
'remove all running instances of that workflow for '
|
||||||
|
'documents of the document type just removed.'
|
||||||
|
),
|
||||||
|
'title': _(
|
||||||
|
'Document types assigned the workflow: %s'
|
||||||
|
) % self.main_object,
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_add(self, queryset, _user):
|
||||||
|
with transaction.atomic():
|
||||||
|
event_workflow_edited.commit(
|
||||||
|
actor=_user, target=self.main_object
|
||||||
|
)
|
||||||
|
|
||||||
|
for obj in queryset:
|
||||||
|
self.main_object.document_types.add(obj)
|
||||||
|
event_document_type_edited.commit(
|
||||||
|
action_object=self.main_object, actor=_user, target=obj
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_remove(self, queryset, _user):
|
||||||
|
with transaction.atomic():
|
||||||
|
event_workflow_edited.commit(
|
||||||
|
actor=_user, target=self.main_object
|
||||||
|
)
|
||||||
|
|
||||||
|
for obj in queryset:
|
||||||
|
self.main_object.document_types.remove(obj)
|
||||||
|
event_document_type_edited.commit(
|
||||||
|
action_object=self.main_object, actor=_user,
|
||||||
|
target=obj
|
||||||
|
)
|
||||||
|
self.main_object.instances.filter(
|
||||||
|
document__document_type=obj
|
||||||
|
).delete()
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTemplatePreviewView(SingleObjectDetailView):
|
||||||
|
form_class = WorkflowPreviewForm
|
||||||
|
model = Workflow
|
||||||
|
object_permission = permission_workflow_view
|
||||||
|
pk_url_kwarg = 'pk'
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_labels': True,
|
||||||
|
'object': self.get_object(),
|
||||||
|
'title': _('Preview of: %s') % self.get_object()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ToolLaunchWorkflows(ConfirmView):
|
||||||
|
extra_context = {
|
||||||
|
'title': _('Launch all workflows?'),
|
||||||
|
'subtitle': _(
|
||||||
|
'This will launch all workflows created after documents have '
|
||||||
|
'already been uploaded.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
view_permission = permission_workflow_tools
|
||||||
|
|
||||||
|
def view_action(self):
|
||||||
|
task_launch_all_workflows.apply_async()
|
||||||
|
messages.success(
|
||||||
|
message=_('Workflow launch queued successfully.'),
|
||||||
|
request=self.request
|
||||||
|
)
|
||||||
File diff suppressed because it is too large
Load Diff
22
mayan/apps/documents/migrations/0048_auto_20190711_0544.py
Normal file
22
mayan/apps/documents/migrations/0048_auto_20190711_0544.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.22 on 2019-07-11 05:44
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import mayan.apps.documents.models.document_version_models
|
||||||
|
import mayan.apps.storage.classes
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('documents', '0047_auto_20180917_0737'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='documentversion',
|
||||||
|
name='file',
|
||||||
|
field=models.FileField(storage=mayan.apps.storage.classes.FakeStorageSubclass(), upload_to=mayan.apps.documents.models.document_version_models.UUID_FUNCTION, verbose_name='File'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -229,7 +229,7 @@ class DocumentPage(models.Model):
|
|||||||
for transformation in transformations:
|
for transformation in transformations:
|
||||||
converter.transform(transformation=transformation)
|
converter.transform(transformation=transformation)
|
||||||
|
|
||||||
return page_image
|
return converter.get_page()
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
# Cleanup in case of error
|
# Cleanup in case of error
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import os
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
from .literals import (
|
from .literals import (
|
||||||
DEFAULT_DOCUMENTS_HASH_BLOCK_SIZE, DEFAULT_LANGUAGE, DEFAULT_LANGUAGE_CODES
|
DEFAULT_DOCUMENTS_HASH_BLOCK_SIZE, DEFAULT_LANGUAGE, DEFAULT_LANGUAGE_CODES
|
||||||
@@ -18,15 +18,14 @@ setting_documentimagecache_storage = namespace.add_setting(
|
|||||||
default='django.core.files.storage.FileSystemStorage', help_text=_(
|
default='django.core.files.storage.FileSystemStorage', help_text=_(
|
||||||
'Path to the Storage subclass to use when storing the cached '
|
'Path to the Storage subclass to use when storing the cached '
|
||||||
'document image files.'
|
'document image files.'
|
||||||
), quoted=True
|
)
|
||||||
)
|
)
|
||||||
setting_documentimagecache_storage_arguments = namespace.add_setting(
|
setting_documentimagecache_storage_arguments = namespace.add_setting(
|
||||||
global_name='DOCUMENTS_CACHE_STORAGE_BACKEND_ARGUMENTS',
|
global_name='DOCUMENTS_CACHE_STORAGE_BACKEND_ARGUMENTS',
|
||||||
default='{{location: {}}}'.format(
|
default={'location': os.path.join(settings.MEDIA_ROOT, 'document_cache')},
|
||||||
os.path.join(settings.MEDIA_ROOT, 'document_cache')
|
help_text=_(
|
||||||
), help_text=_(
|
|
||||||
'Arguments to pass to the DOCUMENT_CACHE_STORAGE_BACKEND.'
|
'Arguments to pass to the DOCUMENT_CACHE_STORAGE_BACKEND.'
|
||||||
), quoted=True,
|
),
|
||||||
)
|
)
|
||||||
setting_disable_base_image_cache = namespace.add_setting(
|
setting_disable_base_image_cache = namespace.add_setting(
|
||||||
global_name='DOCUMENTS_DISABLE_BASE_IMAGE_CACHE', default=False,
|
global_name='DOCUMENTS_DISABLE_BASE_IMAGE_CACHE', default=False,
|
||||||
@@ -127,9 +126,8 @@ setting_storage_backend = namespace.add_setting(
|
|||||||
)
|
)
|
||||||
setting_storage_backend_arguments = namespace.add_setting(
|
setting_storage_backend_arguments = namespace.add_setting(
|
||||||
global_name='DOCUMENTS_STORAGE_BACKEND_ARGUMENTS',
|
global_name='DOCUMENTS_STORAGE_BACKEND_ARGUMENTS',
|
||||||
default='{{location: {}}}'.format(
|
default={'location': os.path.join(settings.MEDIA_ROOT, 'document_storage')},
|
||||||
os.path.join(settings.MEDIA_ROOT, 'document_storage')
|
help_text=_('Arguments to pass to the DOCUMENT_STORAGE_BACKEND.')
|
||||||
), help_text=_('Arguments to pass to the DOCUMENT_STORAGE_BACKEND.')
|
|
||||||
)
|
)
|
||||||
setting_thumbnail_height = namespace.add_setting(
|
setting_thumbnail_height = namespace.add_setting(
|
||||||
global_name='DOCUMENTS_THUMBNAIL_HEIGHT', default='', help_text=_(
|
global_name='DOCUMENTS_THUMBNAIL_HEIGHT', default='', help_text=_(
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import yaml
|
from mayan.apps.storage.utils import get_storage_subclass
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django.utils.module_loading import import_string
|
|
||||||
|
|
||||||
from .settings import (
|
from .settings import (
|
||||||
setting_documentimagecache_storage,
|
setting_documentimagecache_storage,
|
||||||
@@ -15,20 +8,10 @@ from .settings import (
|
|||||||
setting_storage_backend, setting_storage_backend_arguments
|
setting_storage_backend, setting_storage_backend_arguments
|
||||||
)
|
)
|
||||||
|
|
||||||
storage_documentversion = import_string(
|
storage_documentversion = get_storage_subclass(
|
||||||
dotted_path=setting_storage_backend.value
|
dotted_path=setting_storage_backend.value
|
||||||
)(
|
)(**setting_storage_backend_arguments.value)
|
||||||
**yaml.load(
|
|
||||||
stream=setting_storage_backend_arguments.value or '{}',
|
|
||||||
Loader=SafeLoader
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
storage_documentimagecache = import_string(
|
storage_documentimagecache = get_storage_subclass(
|
||||||
dotted_path=setting_documentimagecache_storage.value
|
dotted_path=setting_documentimagecache_storage.value
|
||||||
)(
|
)(**setting_documentimagecache_storage_arguments.value)
|
||||||
**yaml.load(
|
|
||||||
stream=setting_documentimagecache_storage_arguments.value or '{}',
|
|
||||||
Loader=SafeLoader
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from mayan.apps.common.apps import MayanAppConfig
|
from mayan.apps.common.apps import MayanAppConfig
|
||||||
from mayan.apps.common.html_widgets import TwoStateWidget
|
from mayan.apps.common.html_widgets import TwoStateWidget
|
||||||
from mayan.apps.common.menus import (
|
from mayan.apps.common.menus import (
|
||||||
menu_main, menu_object, menu_secondary, menu_tools, menu_topbar, menu_user
|
menu_object, menu_secondary, menu_tools, menu_topbar, menu_user
|
||||||
)
|
)
|
||||||
from mayan.apps.navigation.classes import SourceColumn
|
from mayan.apps.navigation.classes import SourceColumn
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,6 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import sh
|
import sh
|
||||||
import yaml
|
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
@@ -57,11 +51,7 @@ class EXIFToolDriver(FileMetadataDriver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def read_settings(self):
|
def read_settings(self):
|
||||||
driver_arguments = yaml.load(
|
self.exiftool_path = setting_drivers_arguments.value.get(
|
||||||
stream=setting_drivers_arguments.value, Loader=SafeLoader
|
|
||||||
)
|
|
||||||
|
|
||||||
self.exiftool_path = driver_arguments.get(
|
|
||||||
'exif_driver', {}
|
'exif_driver', {}
|
||||||
).get('exiftool_path', DEFAULT_EXIF_PATH)
|
).get('exiftool_path', DEFAULT_EXIF_PATH)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
from .literals import DEFAULT_EXIF_PATH
|
from .literals import DEFAULT_EXIF_PATH
|
||||||
|
|
||||||
@@ -16,12 +16,7 @@ setting_auto_process = namespace.add_setting(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
setting_drivers_arguments = namespace.add_setting(
|
setting_drivers_arguments = namespace.add_setting(
|
||||||
default='''
|
default={'exif_driver': {'exiftool_path': DEFAULT_EXIF_PATH}}, help_text=_(
|
||||||
{{
|
|
||||||
exif_driver: {{exiftool_path: {}}},
|
|
||||||
|
|
||||||
}}
|
|
||||||
'''.replace('\n', '').format(DEFAULT_EXIF_PATH), help_text=_(
|
|
||||||
'Arguments to pass to the drivers.'
|
'Arguments to pass to the drivers.'
|
||||||
), global_name='FILE_METADATA_DRIVERS_ARGUMENTS', quoted=True
|
), global_name='FILE_METADATA_DRIVERS_ARGUMENTS'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
from .literals import DEFAULT_BACKEND, DEFAULT_LOCK_TIMEOUT_VALUE
|
from .literals import DEFAULT_BACKEND, DEFAULT_LOCK_TIMEOUT_VALUE
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ DEFAULT_DOCUMENT_BODY_TEMPLATE = _(
|
|||||||
'--------\n '
|
'--------\n '
|
||||||
'This email has been sent from %(project_title)s (%(project_website)s)'
|
'This email has been sent from %(project_title)s (%(project_website)s)'
|
||||||
)
|
)
|
||||||
|
DEFAULT_DOCUMENT_SUBJECT_TEMPLATE = _('Document: {{ document }}')
|
||||||
DEFAULT_LINK_BODY_TEMPLATE = _(
|
DEFAULT_LINK_BODY_TEMPLATE = _(
|
||||||
'To access this document click on the following link: '
|
'To access this document click on the following link: '
|
||||||
'{{ link }}\n\n--------\n '
|
'{{ link }}\n\n--------\n '
|
||||||
'This email has been sent from %(project_title)s (%(project_website)s)'
|
'This email has been sent from %(project_title)s (%(project_website)s)'
|
||||||
)
|
)
|
||||||
|
DEFAULT_LINK_SUBJECT_TEMPLATE = _('Link for document: {{ document }}')
|
||||||
EMAIL_SEPARATORS = (',', ';')
|
EMAIL_SEPARATORS = (',', ';')
|
||||||
|
|||||||
@@ -2,31 +2,32 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
from .literals import (
|
from .literals import (
|
||||||
DEFAULT_DOCUMENT_BODY_TEMPLATE, DEFAULT_LINK_BODY_TEMPLATE
|
DEFAULT_DOCUMENT_BODY_TEMPLATE, DEFAULT_DOCUMENT_SUBJECT_TEMPLATE,
|
||||||
|
DEFAULT_LINK_BODY_TEMPLATE, DEFAULT_LINK_SUBJECT_TEMPLATE
|
||||||
)
|
)
|
||||||
|
|
||||||
namespace = Namespace(label=_('Mailing'), name='mailer')
|
namespace = Namespace(label=_('Mailing'), name='mailer')
|
||||||
|
|
||||||
setting_link_subject_template = namespace.add_setting(
|
|
||||||
default=_('Link for document: {{ document }}'),
|
|
||||||
help_text=_('Template for the document link email form subject line.'),
|
|
||||||
global_name='MAILER_LINK_SUBJECT_TEMPLATE', quoted=True
|
|
||||||
)
|
|
||||||
setting_link_body_template = namespace.add_setting(
|
|
||||||
default=DEFAULT_LINK_BODY_TEMPLATE,
|
|
||||||
help_text=_('Template for the document link email form body text. Can include HTML.'),
|
|
||||||
global_name='MAILER_LINK_BODY_TEMPLATE', quoted=True
|
|
||||||
)
|
|
||||||
setting_document_subject_template = namespace.add_setting(
|
setting_document_subject_template = namespace.add_setting(
|
||||||
default=_('Document: {{ document }}'),
|
default=DEFAULT_DOCUMENT_SUBJECT_TEMPLATE,
|
||||||
help_text=_('Template for the document email form subject line.'),
|
help_text=_('Template for the document email form subject line.'),
|
||||||
global_name='MAILER_DOCUMENT_SUBJECT_TEMPLATE', quoted=True
|
global_name='MAILER_DOCUMENT_SUBJECT_TEMPLATE'
|
||||||
)
|
)
|
||||||
setting_document_body_template = namespace.add_setting(
|
setting_document_body_template = namespace.add_setting(
|
||||||
default=DEFAULT_DOCUMENT_BODY_TEMPLATE,
|
default=DEFAULT_DOCUMENT_BODY_TEMPLATE,
|
||||||
help_text=_('Template for the document email form body text. Can include HTML.'),
|
help_text=_('Template for the document email form body text. Can include HTML.'),
|
||||||
global_name='MAILER_DOCUMENT_BODY_TEMPLATE', quoted=True
|
global_name='MAILER_DOCUMENT_BODY_TEMPLATE'
|
||||||
|
)
|
||||||
|
setting_link_subject_template = namespace.add_setting(
|
||||||
|
default=DEFAULT_LINK_SUBJECT_TEMPLATE,
|
||||||
|
help_text=_('Template for the document link email form subject line.'),
|
||||||
|
global_name='MAILER_LINK_SUBJECT_TEMPLATE'
|
||||||
|
)
|
||||||
|
setting_link_body_template = namespace.add_setting(
|
||||||
|
default=DEFAULT_LINK_BODY_TEMPLATE,
|
||||||
|
help_text=_('Template for the document link email form body text. Can include HTML.'),
|
||||||
|
global_name='MAILER_LINK_BODY_TEMPLATE'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class EmailActionViewTestCase(DocumentTestMixin, MailerTestMixin, WorkflowTestMi
|
|||||||
self._create_test_user_mailer()
|
self._create_test_user_mailer()
|
||||||
|
|
||||||
response = self.get(
|
response = self.get(
|
||||||
viewname='document_states:setup_workflow_state_action_create',
|
viewname='document_states:workflow_template_state_action_create',
|
||||||
kwargs={
|
kwargs={
|
||||||
'pk': self.test_workflow_state.pk,
|
'pk': self.test_workflow_state.pk,
|
||||||
'class_path': 'mayan.apps.mailer.workflow_actions.EmailAction',
|
'class_path': 'mayan.apps.mailer.workflow_actions.EmailAction',
|
||||||
@@ -151,7 +151,7 @@ class EmailActionViewTestCase(DocumentTestMixin, MailerTestMixin, WorkflowTestMi
|
|||||||
|
|
||||||
def _request_email_action_create_post_view(self):
|
def _request_email_action_create_post_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='document_states:setup_workflow_state_action_create',
|
viewname='document_states:workflow_template_state_action_create',
|
||||||
kwargs={
|
kwargs={
|
||||||
'pk': self.test_workflow_state.pk,
|
'pk': self.test_workflow_state.pk,
|
||||||
'class_path': 'mayan.apps.mailer.workflow_actions.EmailAction',
|
'class_path': 'mayan.apps.mailer.workflow_actions.EmailAction',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
from .parsers import MetadataParser
|
from .parsers import MetadataParser
|
||||||
from .validators import MetadataValidator
|
from .validators import MetadataValidator
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
namespace = Namespace(label=_('Mirroring'), name='mirroring')
|
namespace = Namespace(label=_('Mirroring'), name='mirroring')
|
||||||
|
|
||||||
|
|||||||
@@ -369,10 +369,25 @@ class Menu(object):
|
|||||||
for resolved_navigation_object in resolved_navigation_object_list:
|
for resolved_navigation_object in resolved_navigation_object_list:
|
||||||
resolved_links = []
|
resolved_links = []
|
||||||
|
|
||||||
|
# List of resolved links source links used for deduplication
|
||||||
|
resolved_links_links = []
|
||||||
|
|
||||||
for bound_source, links in self.bound_links.items():
|
for bound_source, links in self.bound_links.items():
|
||||||
try:
|
try:
|
||||||
if inspect.isclass(bound_source):
|
if inspect.isclass(bound_source):
|
||||||
if type(resolved_navigation_object) == bound_source:
|
if type(resolved_navigation_object) == bound_source:
|
||||||
|
# Check to see if object is a proxy model. If it is, add its parent model
|
||||||
|
# menu links too.
|
||||||
|
if hasattr(resolved_navigation_object, '_meta'):
|
||||||
|
parent_model = resolved_navigation_object._meta.proxy_for_model
|
||||||
|
if parent_model:
|
||||||
|
parent_instance = parent_model.objects.filter(pk=resolved_navigation_object.pk)
|
||||||
|
if parent_instance:
|
||||||
|
for link_set in self.resolve(context=context, source=parent_instance.first()):
|
||||||
|
for link in link_set['links']:
|
||||||
|
if link.link not in self.unbound_links.get(bound_source, ()):
|
||||||
|
resolved_links.append(link)
|
||||||
|
|
||||||
for link in links:
|
for link in links:
|
||||||
resolved_link = link.resolve(
|
resolved_link = link.resolve(
|
||||||
context=context,
|
context=context,
|
||||||
@@ -395,10 +410,22 @@ class Menu(object):
|
|||||||
resolved_links.append(resolved_link)
|
resolved_links.append(resolved_link)
|
||||||
# No need for further content object match testing
|
# No need for further content object match testing
|
||||||
break
|
break
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# When source is a dictionary
|
# When source is a dictionary
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Remove duplicated resolved link by using their source link
|
||||||
|
# instance as reference. The actual resolved link can't be used
|
||||||
|
# since a single source link can produce multiple resolved links.
|
||||||
|
# Since dictionaries keys can't have duplicates, we use that as a
|
||||||
|
# native deduplicator.
|
||||||
|
resolved_links_dict = {}
|
||||||
|
for resolved_link in resolved_links:
|
||||||
|
resolved_links_dict[resolved_link.link] = resolved_link
|
||||||
|
|
||||||
|
resolved_links = resolved_links_dict.values()
|
||||||
|
|
||||||
if resolved_links:
|
if resolved_links:
|
||||||
result.append(
|
result.append(
|
||||||
{
|
{
|
||||||
@@ -407,6 +434,7 @@ class Menu(object):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
resolved_links = []
|
resolved_links = []
|
||||||
# View links
|
# View links
|
||||||
for link in self.bound_links.get(current_view_name, []):
|
for link in self.bound_links.get(current_view_name, []):
|
||||||
|
|||||||
@@ -4,11 +4,6 @@ import logging
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
import sh
|
import sh
|
||||||
import yaml
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@@ -115,15 +110,10 @@ class Tesseract(OCRBackendBase):
|
|||||||
logger.debug('Available languages: %s', ', '.join(self.languages))
|
logger.debug('Available languages: %s', ', '.join(self.languages))
|
||||||
|
|
||||||
def read_settings(self):
|
def read_settings(self):
|
||||||
backend_arguments = yaml.load(
|
self.tesseract_binary_path = setting_ocr_backend_arguments.value.get(
|
||||||
Loader=SafeLoader,
|
|
||||||
stream=setting_ocr_backend_arguments.value or '{}',
|
|
||||||
)
|
|
||||||
|
|
||||||
self.tesseract_binary_path = backend_arguments.get(
|
|
||||||
'tesseract_path', DEFAULT_TESSERACT_BINARY_PATH
|
'tesseract_path', DEFAULT_TESSERACT_BINARY_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
self.command_timeout = backend_arguments.get(
|
self.command_timeout = setting_ocr_backend_arguments.value.get(
|
||||||
'timeout', DEFAULT_TESSERACT_TIMEOUT
|
'timeout', DEFAULT_TESSERACT_TIMEOUT
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,20 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
|
||||||
from .settings import setting_ocr_backend, setting_ocr_backend_arguments
|
from .settings import setting_ocr_backend, setting_ocr_backend_arguments
|
||||||
|
|
||||||
ocr_backend = import_string(
|
ocr_backend = import_string(
|
||||||
dotted_path=setting_ocr_backend.value
|
dotted_path=setting_ocr_backend.value
|
||||||
)(
|
)(**setting_ocr_backend_arguments.value)
|
||||||
**yaml.load(
|
|
||||||
stream=setting_ocr_backend_arguments.value or '{}', Loader=SafeLoader
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.smart_settings import Namespace
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
namespace = Namespace(label=_('OCR'), name='ocr')
|
namespace = Namespace(label=_('OCR'), name='ocr')
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ setting_ocr_backend = namespace.add_setting(
|
|||||||
)
|
)
|
||||||
setting_ocr_backend_arguments = namespace.add_setting(
|
setting_ocr_backend_arguments = namespace.add_setting(
|
||||||
global_name='OCR_BACKEND_ARGUMENTS',
|
global_name='OCR_BACKEND_ARGUMENTS',
|
||||||
default=''
|
default={}
|
||||||
)
|
)
|
||||||
setting_auto_ocr = namespace.add_setting(
|
setting_auto_ocr = namespace.add_setting(
|
||||||
global_name='OCR_AUTO_OCR', default=True,
|
global_name='OCR_AUTO_OCR', default=True,
|
||||||
|
|||||||
@@ -2,16 +2,12 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import yaml
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
|
from django.utils.html import mark_safe
|
||||||
from django.utils.encoding import force_text, python_2_unicode_compatible
|
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.common.serialization import yaml_dump, yaml_load
|
||||||
from mayan.apps.common.settings import (
|
from mayan.apps.common.settings import (
|
||||||
setting_celery_broker_url, setting_celery_result_backend
|
setting_celery_broker_url, setting_celery_result_backend
|
||||||
)
|
)
|
||||||
@@ -24,9 +20,25 @@ class Variable(object):
|
|||||||
self.default = default
|
self.default = default
|
||||||
self.environment_name = environment_name
|
self.environment_name = environment_name
|
||||||
|
|
||||||
def get_value(self):
|
def _get_value(self):
|
||||||
return os.environ.get(self.environment_name, self.default)
|
return os.environ.get(self.environment_name, self.default)
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
return mark_safe(self._get_value())
|
||||||
|
|
||||||
|
|
||||||
|
class YAMLVariable(Variable):
|
||||||
|
def _get_value(self):
|
||||||
|
value = os.environ.get(self.environment_name)
|
||||||
|
if value:
|
||||||
|
value = yaml_load(stream=value)
|
||||||
|
else:
|
||||||
|
value = self.default
|
||||||
|
|
||||||
|
return yaml_dump(
|
||||||
|
data=value, allow_unicode=True, default_flow_style=True, width=999
|
||||||
|
).replace('...\n', '').replace('\n', '')
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class PlatformTemplate(object):
|
class PlatformTemplate(object):
|
||||||
@@ -95,9 +107,7 @@ class PlatformTemplate(object):
|
|||||||
|
|
||||||
if context_string:
|
if context_string:
|
||||||
context.update(
|
context.update(
|
||||||
yaml.load(
|
yaml_load(stream=context_string)
|
||||||
stream=context_string, Loader=SafeLoader
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return loader.render_to_string(
|
return loader.render_to_string(
|
||||||
template_name=self.get_template_name(),
|
template_name=self.get_template_name(),
|
||||||
@@ -106,10 +116,6 @@ class PlatformTemplate(object):
|
|||||||
|
|
||||||
|
|
||||||
class PlatformTemplateSupervisord(PlatformTemplate):
|
class PlatformTemplateSupervisord(PlatformTemplate):
|
||||||
context_defaults = {
|
|
||||||
'BROKER_URL': 'redis://127.0.0.1:6379/0',
|
|
||||||
'CELERY_RESULT_BACKEND': 'redis://127.0.0.1:6379/0',
|
|
||||||
}
|
|
||||||
label = _('Template for Supervisord.')
|
label = _('Template for Supervisord.')
|
||||||
name = 'supervisord'
|
name = 'supervisord'
|
||||||
settings = (
|
settings = (
|
||||||
@@ -124,35 +130,38 @@ class PlatformTemplateSupervisord(PlatformTemplate):
|
|||||||
name='GUNICORN_TIMEOUT', default=120,
|
name='GUNICORN_TIMEOUT', default=120,
|
||||||
environment_name='MAYAN_GUNICORN_TIMEOUT'
|
environment_name='MAYAN_GUNICORN_TIMEOUT'
|
||||||
),
|
),
|
||||||
Variable(
|
|
||||||
name='DATABASE_CONN_MAX_AGE', default=0,
|
|
||||||
environment_name='MAYAN_DATABASE_CONN_MAX_AGE'
|
|
||||||
),
|
|
||||||
Variable(
|
|
||||||
name='DATABASE_ENGINE', default='django.db.backends.postgresql',
|
|
||||||
environment_name='MAYAN_DATABASE_ENGINE'
|
|
||||||
),
|
|
||||||
Variable(
|
|
||||||
name='DATABASE_HOST', default='127.0.0.1',
|
|
||||||
environment_name='MAYAN_DATABASE_HOST'
|
|
||||||
),
|
|
||||||
Variable(
|
|
||||||
name='DATABASE_NAME', default='mayan',
|
|
||||||
environment_name='MAYAN_DATABASE_NAME'
|
|
||||||
),
|
|
||||||
Variable(
|
|
||||||
name='DATABASE_PASSWORD', default='mayanuserpass',
|
|
||||||
environment_name='MAYAN_DATABASE_PASSWORD'
|
|
||||||
),
|
|
||||||
Variable(
|
|
||||||
name='DATABASE_USER', default='mayan',
|
|
||||||
environment_name='MAYAN_DATABASE_USER'
|
|
||||||
),
|
|
||||||
Variable(
|
Variable(
|
||||||
name='INSTALLATION_PATH', default='/opt/mayan-edms',
|
name='INSTALLATION_PATH', default='/opt/mayan-edms',
|
||||||
environment_name='MAYAN_INSTALLATION_PATH'
|
environment_name='MAYAN_INSTALLATION_PATH'
|
||||||
),
|
),
|
||||||
Variable(
|
YAMLVariable(
|
||||||
|
name='ALLOWED_HOSTS',
|
||||||
|
default=['*'],
|
||||||
|
environment_name='MAYAN_ALLOWED_HOSTS'
|
||||||
|
),
|
||||||
|
YAMLVariable(
|
||||||
|
name='BROKER_URL',
|
||||||
|
default='redis://127.0.0.1:6379/0',
|
||||||
|
environment_name='MAYAN_BROKER_URL'
|
||||||
|
),
|
||||||
|
YAMLVariable(
|
||||||
|
name='CELERY_RESULT_BACKEND',
|
||||||
|
default='redis://127.0.0.1:6379/0',
|
||||||
|
environment_name='MAYAN_CELERY_RESULT_BACKEND'
|
||||||
|
),
|
||||||
|
YAMLVariable(
|
||||||
|
name='DATABASES',
|
||||||
|
default={
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
|
'NAME': 'mayan', 'PASSWORD':'mayanuserpass',
|
||||||
|
'USER': 'mayan', 'HOST':'127.0.0.1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
environment_name='MAYAN_DATABASES'
|
||||||
|
),
|
||||||
|
YAMLVariable
|
||||||
|
(
|
||||||
name='MEDIA_ROOT', default='/opt/mayan-edms/media',
|
name='MEDIA_ROOT', default='/opt/mayan-edms/media',
|
||||||
environment_name='MAYAN_MEDIA_ROOT'
|
environment_name='MAYAN_MEDIA_ROOT'
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
[supervisord]
|
[supervisord]
|
||||||
environment=
|
environment=
|
||||||
MAYAN_ALLOWED_HOSTS='["*"]', # Allow access to other network hosts other than localhost
|
PYTHONPATH={{ INSTALLATION_PATH }}/lib/python2.7/site-packages:{{ MEDIA_ROOT }}/mayan_settings,
|
||||||
|
DJANGO_SETTINGS_MODULE=mayan.settings.production,
|
||||||
|
MAYAN_MEDIA_ROOT="{{ MEDIA_ROOT }}",
|
||||||
|
MAYAN_ALLOWED_HOSTS="{{ ALLOWED_HOSTS }}",
|
||||||
MAYAN_CELERY_RESULT_BACKEND="{{ CELERY_RESULT_BACKEND }}",
|
MAYAN_CELERY_RESULT_BACKEND="{{ CELERY_RESULT_BACKEND }}",
|
||||||
MAYAN_BROKER_URL="{{ BROKER_URL }}",
|
MAYAN_BROKER_URL="{{ BROKER_URL }}",
|
||||||
PYTHONPATH={{ INSTALLATION_PATH }}/lib/python2.7/site-packages:{{ MEDIA_ROOT }}/mayan_settings,
|
MAYAN_DATABASES="{{ DATABASES }}"
|
||||||
MAYAN_MEDIA_ROOT={{ MEDIA_ROOT }},
|
|
||||||
MAYAN_DATABASE_ENGINE={{ DATABASE_ENGINE }},
|
|
||||||
MAYAN_DATABASE_HOST={{ DATABASE_HOST }},
|
|
||||||
MAYAN_DATABASE_NAME={{ DATABASE_NAME }},
|
|
||||||
MAYAN_DATABASE_PASSWORD={{ DATABASE_PASSWORD }},
|
|
||||||
MAYAN_DATABASE_USER={{ DATABASE_USER }},
|
|
||||||
MAYAN_DATABASE_CONN_MAX_AGE={{ DATABASE_CONN_MAX_AGE }},
|
|
||||||
DJANGO_SETTINGS_MODULE=mayan.settings.production
|
|
||||||
|
|
||||||
[program:mayan-gunicorn]
|
[program:mayan-gunicorn]
|
||||||
autorestart = true
|
autorestart = true
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .classes import Namespace, Setting # NOQA
|
|
||||||
|
|
||||||
default_app_config = 'mayan.apps.smart_settings.apps.SmartSettingsApp'
|
default_app_config = 'mayan.apps.smart_settings.apps.SmartSettingsApp'
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from .links import (
|
|||||||
link_namespace_detail, link_namespace_list, link_namespace_root_list,
|
link_namespace_detail, link_namespace_list, link_namespace_root_list,
|
||||||
link_setting_edit
|
link_setting_edit
|
||||||
)
|
)
|
||||||
|
from .settings import * # NOQA
|
||||||
from .widgets import setting_widget
|
from .widgets import setting_widget
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ import sys
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader, CSafeDumper as SafeDumper
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader, SafeDumper
|
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.functional import Promise
|
from django.utils.functional import Promise
|
||||||
@@ -21,6 +16,10 @@ from django.utils.encoding import (
|
|||||||
force_bytes, force_text, python_2_unicode_compatible
|
force_bytes, force_text, python_2_unicode_compatible
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from mayan.apps.common.serialization import yaml_dump, yaml_load
|
||||||
|
|
||||||
|
from .utils import read_configuration_file
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -82,10 +81,11 @@ class Namespace(object):
|
|||||||
class Setting(object):
|
class Setting(object):
|
||||||
_registry = {}
|
_registry = {}
|
||||||
_cache_hash = None
|
_cache_hash = None
|
||||||
|
_config_file_cache = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def deserialize_value(value):
|
def deserialize_value(value):
|
||||||
return yaml.load(stream=value, Loader=SafeLoader)
|
return yaml_load(stream=value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def express_promises(value):
|
def express_promises(value):
|
||||||
@@ -101,9 +101,9 @@ class Setting(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def serialize_value(value):
|
def serialize_value(value):
|
||||||
result = yaml.dump(
|
result = yaml_dump(
|
||||||
data=Setting.express_promises(value), allow_unicode=True,
|
data=Setting.express_promises(value), allow_unicode=True,
|
||||||
Dumper=SafeDumper
|
default_flow_style=False,
|
||||||
)
|
)
|
||||||
# safe_dump returns bytestrings
|
# safe_dump returns bytestrings
|
||||||
# Disregard the last 3 dots that mark the end of the YAML document
|
# Disregard the last 3 dots that mark the end of the YAML document
|
||||||
@@ -128,8 +128,8 @@ class Setting(object):
|
|||||||
if (filter_term and filter_term.lower() in setting.global_name.lower()) or not filter_term:
|
if (filter_term and filter_term.lower() in setting.global_name.lower()) or not filter_term:
|
||||||
dictionary[setting.global_name] = Setting.express_promises(setting.value)
|
dictionary[setting.global_name] = Setting.express_promises(setting.value)
|
||||||
|
|
||||||
return yaml.dump(
|
return yaml_dump(
|
||||||
data=dictionary, default_flow_style=False, Dumper=SafeDumper
|
data=dictionary, default_flow_style=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -140,6 +140,16 @@ class Setting(object):
|
|||||||
def get_all(cls):
|
def get_all(cls):
|
||||||
return sorted(cls._registry.values(), key=lambda x: x.global_name)
|
return sorted(cls._registry.values(), key=lambda x: x.global_name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_config_file_content(cls):
|
||||||
|
# Cache content of config file to speed up initial boot up
|
||||||
|
if not cls._config_file_cache:
|
||||||
|
cls._config_file_cache = read_configuration_file(
|
||||||
|
path=settings.CONFIGURATION_FILEPATH
|
||||||
|
)
|
||||||
|
|
||||||
|
return cls._config_file_cache
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_hash(cls):
|
def get_hash(cls):
|
||||||
return force_text(
|
return force_text(
|
||||||
@@ -167,13 +177,12 @@ class Setting(object):
|
|||||||
path=settings.CONFIGURATION_LAST_GOOD_FILEPATH
|
path=settings.CONFIGURATION_LAST_GOOD_FILEPATH
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, namespace, global_name, default, help_text=None, is_path=False, quoted=False):
|
def __init__(self, namespace, global_name, default, help_text=None, is_path=False):
|
||||||
self.global_name = global_name
|
self.global_name = global_name
|
||||||
self.default = default
|
self.default = default
|
||||||
self.help_text = help_text
|
self.help_text = help_text
|
||||||
self.loaded = False
|
self.loaded = False
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
self.quoted = quoted
|
|
||||||
self.environment_variable = False
|
self.environment_variable = False
|
||||||
namespace._settings.append(self)
|
namespace._settings.append(self)
|
||||||
self.__class__._registry[global_name] = self
|
self.__class__._registry[global_name] = self
|
||||||
@@ -186,7 +195,7 @@ class Setting(object):
|
|||||||
if environment_value:
|
if environment_value:
|
||||||
self.environment_variable = True
|
self.environment_variable = True
|
||||||
try:
|
try:
|
||||||
self.raw_value = environment_value
|
self.raw_value = yaml_load(stream=environment_value)
|
||||||
except yaml.YAMLError as exception:
|
except yaml.YAMLError as exception:
|
||||||
raise type(exception)(
|
raise type(exception)(
|
||||||
'Error interpreting environment variable: {} with '
|
'Error interpreting environment variable: {} with '
|
||||||
@@ -195,7 +204,12 @@ class Setting(object):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.raw_value = getattr(settings, self.global_name, self.default)
|
self.raw_value = self.get_config_file_content().get(
|
||||||
|
self.global_name, getattr(
|
||||||
|
settings, self.global_name, self.default
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.yaml = Setting.serialize_value(self.raw_value)
|
self.yaml = Setting.serialize_value(self.raw_value)
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.common.serialization import yaml_load
|
||||||
|
|
||||||
|
|
||||||
class SettingForm(forms.Form):
|
class SettingForm(forms.Form):
|
||||||
value = forms.CharField(
|
value = forms.CharField(
|
||||||
@@ -25,20 +22,8 @@ class SettingForm(forms.Form):
|
|||||||
self.fields['value'].initial = self.setting.serialized_value
|
self.fields['value'].initial = self.setting.serialized_value
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
quotes = ['"', "'"]
|
|
||||||
|
|
||||||
if self.setting.quoted:
|
|
||||||
stripped = self.cleaned_data['value'].strip()
|
|
||||||
|
|
||||||
if stripped[0] not in quotes or stripped[-1] not in quotes:
|
|
||||||
raise ValidationError(
|
|
||||||
_(
|
|
||||||
'Value must be properly quoted.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yaml.load(stream=self.cleaned_data['value'], Loader=SafeLoader)
|
yaml_load(stream=self.cleaned_data['value'])
|
||||||
except yaml.YAMLError:
|
except yaml.YAMLError:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_(
|
_(
|
||||||
|
|||||||
36
mayan/apps/smart_settings/literals.py
Normal file
36
mayan/apps/smart_settings/literals.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# Default in YAML format
|
||||||
|
BOOTSTRAP_SETTING_LIST = (
|
||||||
|
{'name': 'ALLOWED_HOSTS', 'default': "['127.0.0.1', 'localhost', '[::1]']"},
|
||||||
|
{'name': 'APPEND_SLASH'},
|
||||||
|
{'name': 'AUTH_PASSWORD_VALIDATORS'},
|
||||||
|
{'name': 'COMMON_DISABLED_APPS'},
|
||||||
|
{'name': 'COMMON_EXTRA_APPS'},
|
||||||
|
{'name': 'DATA_UPLOAD_MAX_MEMORY_SIZE'},
|
||||||
|
{'name': 'DATABASES'},
|
||||||
|
{'name': 'DEBUG', 'default': 'false'},
|
||||||
|
{'name': 'DEFAULT_FROM_EMAIL'},
|
||||||
|
{'name': 'DISALLOWED_USER_AGENTS'},
|
||||||
|
{'name': 'EMAIL_BACKEND'},
|
||||||
|
{'name': 'EMAIL_HOST'},
|
||||||
|
{'name': 'EMAIL_HOST_PASSWORD'},
|
||||||
|
{'name': 'EMAIL_HOST_USER'},
|
||||||
|
{'name': 'EMAIL_PORT'},
|
||||||
|
{'name': 'EMAIL_TIMEOUT'},
|
||||||
|
{'name': 'EMAIL_USE_SSL'},
|
||||||
|
{'name': 'EMAIL_USE_TLS'},
|
||||||
|
{'name': 'FILE_UPLOAD_MAX_MEMORY_SIZE'},
|
||||||
|
{'name': 'HOME_VIEW'},
|
||||||
|
{'name': 'INSTALLED_APPS'},
|
||||||
|
{'name': 'INTERNAL_IPS', 'default': "['127.0.0.1']"},
|
||||||
|
{'name': 'LANGUAGES'},
|
||||||
|
{'name': 'LANGUAGE_CODE'},
|
||||||
|
{'name': 'LOGIN_REDIRECT_URL', 'default': 'common:home'},
|
||||||
|
{'name': 'LOGIN_URL', 'default': 'authentication:login_view'},
|
||||||
|
{'name': 'LOGOUT_REDIRECT_URL', 'default': 'authentication:login_view'},
|
||||||
|
{'name': 'STATIC_URL'},
|
||||||
|
{'name': 'STATICFILES_STORAGE'},
|
||||||
|
{'name': 'TIME_ZONE'},
|
||||||
|
{'name': 'WSGI_APPLICATION'}
|
||||||
|
)
|
||||||
302
mayan/apps/smart_settings/settings.py
Normal file
302
mayan/apps/smart_settings/settings.py
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.smart_settings.classes import Namespace
|
||||||
|
|
||||||
|
# Don't import anything on start import, we just want to make it easy
|
||||||
|
# for apps.py to activate the settings in this module.
|
||||||
|
__all__ = ()
|
||||||
|
namespace = Namespace(label=_('Django'), name='django')
|
||||||
|
|
||||||
|
setting_django_allowed_hosts = namespace.add_setting(
|
||||||
|
global_name='ALLOWED_HOSTS', default=settings.ALLOWED_HOSTS,
|
||||||
|
help_text=_(
|
||||||
|
'A list of strings representing the host/domain names that this site '
|
||||||
|
'can serve. This is a security measure to prevent HTTP Host header '
|
||||||
|
'attacks, which are possible even under many seemingly-safe web '
|
||||||
|
'server configurations. Values in this list can be '
|
||||||
|
'fully qualified names (e.g. \'www.example.com\'), in which case '
|
||||||
|
'they will be matched against the request\'s Host header exactly '
|
||||||
|
'(case-insensitive, not including port). A value beginning with a '
|
||||||
|
'period can be used as a subdomain wildcard: \'.example.com\' will '
|
||||||
|
'match example.com, www.example.com, and any other subdomain of '
|
||||||
|
'example.com. A value of \'*\' will match anything; in this case you '
|
||||||
|
'are responsible to provide your own validation of the Host header '
|
||||||
|
'(perhaps in a middleware; if so this middleware must be listed '
|
||||||
|
'first in MIDDLEWARE).'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_append_slash = namespace.add_setting(
|
||||||
|
global_name='APPEND_SLASH', default=settings.APPEND_SLASH,
|
||||||
|
help_text=_(
|
||||||
|
'When set to True, if the request URL does not match any of the '
|
||||||
|
'patterns in the URLconf and it doesn\'t end in a slash, an HTTP '
|
||||||
|
'redirect is issued to the same URL with a slash appended. Note '
|
||||||
|
'that the redirect may cause any data submitted in a POST request '
|
||||||
|
'to be lost. The APPEND_SLASH setting is only used if '
|
||||||
|
'CommonMiddleware is installed (see Middleware). See also '
|
||||||
|
'PREPEND_WWW.'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setting_django_auth_password_validators = namespace.add_setting(
|
||||||
|
global_name='AUTH_PASSWORD_VALIDATORS',
|
||||||
|
default=settings.AUTH_PASSWORD_VALIDATORS,
|
||||||
|
help_text=_(
|
||||||
|
'The list of validators that are used to check the strength of '
|
||||||
|
'user\'s passwords.'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setting_django_databases = namespace.add_setting(
|
||||||
|
global_name='DATABASES', default=settings.DATABASES,
|
||||||
|
help_text=_(
|
||||||
|
'A dictionary containing the settings for all databases to be used '
|
||||||
|
'with Django. It is a nested dictionary whose contents map a '
|
||||||
|
'database alias to a dictionary containing the options for an '
|
||||||
|
'individual database. The DATABASES setting must configure a '
|
||||||
|
'default database; any number of additional databases may also '
|
||||||
|
'be specified.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_data_upload_max_memory_size = namespace.add_setting(
|
||||||
|
global_name='DATA_UPLOAD_MAX_MEMORY_SIZE',
|
||||||
|
default=settings.DATA_UPLOAD_MAX_MEMORY_SIZE,
|
||||||
|
help_text=_(
|
||||||
|
'Default: 2621440 (i.e. 2.5 MB). The maximum size in bytes that a '
|
||||||
|
'request body may be before a SuspiciousOperation '
|
||||||
|
'(RequestDataTooBig) is raised. The check is done when accessing '
|
||||||
|
'request.body or request.POST and is calculated against the total '
|
||||||
|
'request size excluding any file upload data. You can set this to '
|
||||||
|
'None to disable the check. Applications that are expected to '
|
||||||
|
'receive unusually large form posts should tune this setting. The '
|
||||||
|
'amount of request data is correlated to the amount of memory '
|
||||||
|
'needed to process the request and populate the GET and POST '
|
||||||
|
'dictionaries. Large requests could be used as a '
|
||||||
|
'denial-of-service attack vector if left unchecked. Since web '
|
||||||
|
'servers don\'t typically perform deep request inspection, it\'s '
|
||||||
|
'not possible to perform a similar check at that level. See also '
|
||||||
|
'FILE_UPLOAD_MAX_MEMORY_SIZE.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_default_from_email = namespace.add_setting(
|
||||||
|
global_name='DEFAULT_FROM_EMAIL',
|
||||||
|
default=settings.DEFAULT_FROM_EMAIL,
|
||||||
|
help_text=_(
|
||||||
|
'Default: \'webmaster@localhost\' '
|
||||||
|
'Default email address to use for various automated correspondence '
|
||||||
|
'from the site manager(s). This doesn\'t include error messages sent '
|
||||||
|
'to ADMINS and MANAGERS; for that, see SERVER_EMAIL.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_disallowed_user_agents = namespace.add_setting(
|
||||||
|
global_name='DISALLOWED_USER_AGENTS',
|
||||||
|
default=settings.DISALLOWED_USER_AGENTS,
|
||||||
|
help_text=_(
|
||||||
|
'Default: [] (Empty list). List of compiled regular expression '
|
||||||
|
'objects representing User-Agent strings that are not allowed to '
|
||||||
|
'visit any page, systemwide. Use this for bad robots/crawlers. '
|
||||||
|
'This is only used if CommonMiddleware is installed '
|
||||||
|
'(see Middleware).'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_email_backend = namespace.add_setting(
|
||||||
|
global_name='EMAIL_BACKEND',
|
||||||
|
default=settings.EMAIL_BACKEND,
|
||||||
|
help_text=_(
|
||||||
|
'Default: \'django.core.mail.backends.smtp.EmailBackend\'. The '
|
||||||
|
'backend to use for sending emails.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_email_host = namespace.add_setting(
|
||||||
|
global_name='EMAIL_HOST',
|
||||||
|
default=settings.EMAIL_HOST,
|
||||||
|
help_text=_(
|
||||||
|
'Default: \'localhost\'. The host to use for sending email.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_email_host_password = namespace.add_setting(
|
||||||
|
global_name='EMAIL_HOST_PASSWORD',
|
||||||
|
default=settings.EMAIL_HOST_PASSWORD,
|
||||||
|
help_text=_(
|
||||||
|
'Default: \'\' (Empty string). Password to use for the SMTP '
|
||||||
|
'server defined in EMAIL_HOST. This setting is used in '
|
||||||
|
'conjunction with EMAIL_HOST_USER when authenticating to the '
|
||||||
|
'SMTP server. If either of these settings is empty, '
|
||||||
|
'Django won\'t attempt authentication.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_email_host_user = namespace.add_setting(
|
||||||
|
global_name='EMAIL_HOST_USER',
|
||||||
|
default=settings.EMAIL_HOST_USER,
|
||||||
|
help_text=_(
|
||||||
|
'Default: \'\' (Empty string). Username to use for the SMTP '
|
||||||
|
'server defined in EMAIL_HOST. If empty, Django won\'t attempt '
|
||||||
|
'authentication.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_email_port = namespace.add_setting(
|
||||||
|
global_name='EMAIL_PORT',
|
||||||
|
default=settings.EMAIL_PORT,
|
||||||
|
help_text=_(
|
||||||
|
'Default: 25. Port to use for the SMTP server defined in EMAIL_HOST.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_email_timeout = namespace.add_setting(
|
||||||
|
global_name='EMAIL_TIMEOUT',
|
||||||
|
default=settings.EMAIL_TIMEOUT,
|
||||||
|
help_text=_(
|
||||||
|
'Default: None. Specifies a timeout in seconds for blocking '
|
||||||
|
'operations like the connection attempt.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_email_user_tls = namespace.add_setting(
|
||||||
|
global_name='EMAIL_USE_TLS',
|
||||||
|
default=settings.EMAIL_USE_TLS,
|
||||||
|
help_text=_(
|
||||||
|
'Default: False. Whether to use a TLS (secure) connection when '
|
||||||
|
'talking to the SMTP server. This is used for explicit TLS '
|
||||||
|
'connections, generally on port 587. If you are experiencing '
|
||||||
|
'hanging connections, see the implicit TLS setting EMAIL_USE_SSL.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_email_user_ssl = namespace.add_setting(
|
||||||
|
global_name='EMAIL_USE_SSL',
|
||||||
|
default=settings.EMAIL_USE_SSL,
|
||||||
|
help_text=_(
|
||||||
|
'Default: False. Whether to use an implicit TLS (secure) connection '
|
||||||
|
'when talking to the SMTP server. In most email documentation this '
|
||||||
|
'type of TLS connection is referred to as SSL. It is generally used '
|
||||||
|
'on port 465. If you are experiencing problems, see the explicit '
|
||||||
|
'TLS setting EMAIL_USE_TLS. Note that EMAIL_USE_TLS/EMAIL_USE_SSL '
|
||||||
|
'are mutually exclusive, so only set one of those settings to True.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_file_upload_max_memory_size = namespace.add_setting(
|
||||||
|
global_name='FILE_UPLOAD_MAX_MEMORY_SIZE',
|
||||||
|
default=settings.FILE_UPLOAD_MAX_MEMORY_SIZE,
|
||||||
|
help_text=_(
|
||||||
|
'Default: 2621440 (i.e. 2.5 MB). The maximum size (in bytes) '
|
||||||
|
'that an upload will be before it gets streamed to the file '
|
||||||
|
'system. See Managing files for details. See also '
|
||||||
|
'DATA_UPLOAD_MAX_MEMORY_SIZE.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_login_url = namespace.add_setting(
|
||||||
|
global_name='LOGIN_URL',
|
||||||
|
default=settings.LOGIN_URL,
|
||||||
|
help_text=_(
|
||||||
|
'Default: \'/accounts/login/\' The URL where requests are '
|
||||||
|
'redirected for login, especially when using the login_required() '
|
||||||
|
'decorator. This setting also accepts named URL patterns which '
|
||||||
|
'can be used to reduce configuration duplication since you '
|
||||||
|
'don\'t have to define the URL in two places (settings '
|
||||||
|
'and URLconf).'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setting_django_login_redirect_url = namespace.add_setting(
|
||||||
|
global_name='LOGIN_REDIRECT_URL',
|
||||||
|
default=settings.LOGIN_REDIRECT_URL,
|
||||||
|
help_text=_(
|
||||||
|
'Default: \'/accounts/profile/\' The URL where requests are '
|
||||||
|
'redirected after login when the contrib.auth.login view gets no '
|
||||||
|
'next parameter. This is used by the login_required() decorator, '
|
||||||
|
'for example. This setting also accepts named URL patterns which '
|
||||||
|
'can be used to reduce configuration duplication since you don\'t '
|
||||||
|
'have to define the URL in two places (settings and URLconf).'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_logout_redirect_url = namespace.add_setting(
|
||||||
|
global_name='LOGOUT_REDIRECT_URL',
|
||||||
|
default=settings.LOGOUT_REDIRECT_URL,
|
||||||
|
help_text=_(
|
||||||
|
'Default: None. The URL where requests are redirected after a user '
|
||||||
|
'logs out using LogoutView (if the view doesn\'t get a next_page '
|
||||||
|
'argument). If None, no redirect will be performed and the logout '
|
||||||
|
'view will be rendered. This setting also accepts named URL '
|
||||||
|
'patterns which can be used to reduce configuration duplication '
|
||||||
|
'since you don\'t have to define the URL in two places (settings '
|
||||||
|
'and URLconf).'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setting_django_internal_ips = namespace.add_setting(
|
||||||
|
global_name='INTERNAL_IPS',
|
||||||
|
default=settings.INTERNAL_IPS,
|
||||||
|
help_text=_(
|
||||||
|
'A list of IP addresses, as strings, that: Allow the debug() '
|
||||||
|
'context processor to add some variables to the template context. '
|
||||||
|
'Can use the admindocs bookmarklets even if not logged in as a '
|
||||||
|
'staff user. Are marked as "internal" (as opposed to "EXTERNAL") '
|
||||||
|
'in AdminEmailHandler emails.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_languages = namespace.add_setting(
|
||||||
|
global_name='LANGUAGES',
|
||||||
|
default=settings.LANGUAGES,
|
||||||
|
help_text=_(
|
||||||
|
'A list of all available languages. The list is a list of '
|
||||||
|
'two-tuples in the format (language code, language name) '
|
||||||
|
'for example, (\'ja\', \'Japanese\'). This specifies which '
|
||||||
|
'languages are available for language selection. '
|
||||||
|
'Generally, the default value should suffice. Only set this '
|
||||||
|
'setting if you want to restrict language selection to a '
|
||||||
|
'subset of the Django-provided languages. '
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_language_code = namespace.add_setting(
|
||||||
|
global_name='LANGUAGE_CODE',
|
||||||
|
default=settings.LANGUAGE_CODE,
|
||||||
|
help_text=_(
|
||||||
|
'A string representing the language code for this installation. '
|
||||||
|
'This should be in standard language ID format. For example, U.S. '
|
||||||
|
'English is "en-us". It serves two purposes: If the locale '
|
||||||
|
'middleware isn\'t in use, it decides which translation is served '
|
||||||
|
'to all users. If the locale middleware is active, it provides a '
|
||||||
|
'fallback language in case the user\'s preferred language can\'t '
|
||||||
|
'be determined or is not supported by the website. It also provides '
|
||||||
|
'the fallback translation when a translation for a given literal '
|
||||||
|
'doesn\'t exist for the user\'s preferred language.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_static_url = namespace.add_setting(
|
||||||
|
global_name='STATIC_URL',
|
||||||
|
default=settings.STATIC_URL,
|
||||||
|
help_text=_(
|
||||||
|
'URL to use when referring to static files located in STATIC_ROOT. '
|
||||||
|
'Example: "/static/" or "http://static.example.com/" '
|
||||||
|
'If not None, this will be used as the base path for asset '
|
||||||
|
'definitions (the Media class) and the staticfiles app. '
|
||||||
|
'It must end in a slash if set to a non-empty value.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_staticfiles_storage = namespace.add_setting(
|
||||||
|
global_name='STATICFILES_STORAGE',
|
||||||
|
default=settings.STATICFILES_STORAGE,
|
||||||
|
help_text=_(
|
||||||
|
'The file storage engine to use when collecting static files with '
|
||||||
|
'the collectstatic management command. A ready-to-use instance of '
|
||||||
|
'the storage backend defined in this setting can be found at '
|
||||||
|
'django.contrib.staticfiles.storage.staticfiles_storage.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_time_zone = namespace.add_setting(
|
||||||
|
global_name='TIME_ZONE',
|
||||||
|
default=settings.TIME_ZONE,
|
||||||
|
help_text=_(
|
||||||
|
'A string representing the time zone for this installation. '
|
||||||
|
'Note that this isn\'t necessarily the time zone of the server. '
|
||||||
|
'For example, one server may serve multiple Django-powered sites, '
|
||||||
|
'each with a separate time zone setting.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setting_django_wsgi_application = namespace.add_setting(
|
||||||
|
global_name='WSGI_APPLICATION',
|
||||||
|
default=settings.WSGI_APPLICATION,
|
||||||
|
help_text=_(
|
||||||
|
'The full Python path of the WSGI application object that Django\'s '
|
||||||
|
'built-in servers (e.g. runserver) will use. The django-admin '
|
||||||
|
'startproject management command will create a simple wsgi.py '
|
||||||
|
'file with an application callable in it, and point this setting '
|
||||||
|
'to that application.'
|
||||||
|
),
|
||||||
|
)
|
||||||
@@ -11,7 +11,7 @@ from mayan.apps.common.settings import setting_paginate_by
|
|||||||
from mayan.apps.common.tests import BaseTestCase
|
from mayan.apps.common.tests import BaseTestCase
|
||||||
from mayan.apps.storage.utils import fs_cleanup
|
from mayan.apps.storage.utils import fs_cleanup
|
||||||
|
|
||||||
from ..classes import Namespace, Setting
|
from ..classes import Setting
|
||||||
|
|
||||||
from .literals import ENVIRONMENT_TEST_NAME, ENVIRONMENT_TEST_VALUE
|
from .literals import ENVIRONMENT_TEST_NAME, ENVIRONMENT_TEST_VALUE
|
||||||
from .mixins import SmartSettingTestMixin
|
from .mixins import SmartSettingTestMixin
|
||||||
@@ -48,8 +48,8 @@ class ClassesTestCase(SmartSettingTestMixin, BaseTestCase):
|
|||||||
default='test value'
|
default='test value'
|
||||||
)
|
)
|
||||||
# Initialize hash cache
|
# Initialize hash cache
|
||||||
|
Setting._cache_hash = None
|
||||||
Setting.check_changed()
|
Setting.check_changed()
|
||||||
self.assertFalse(Setting.check_changed())
|
self.assertFalse(Setting.check_changed())
|
||||||
test_setting.value = 'test value edited'
|
test_setting.value = 'test value edited'
|
||||||
self.assertTrue(Setting.check_changed())
|
self.assertTrue(Setting.check_changed())
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ from .views import NamespaceDetailView, NamespaceListView, SettingEditView
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(
|
url(
|
||||||
regex=r'^namespace/all/$', view=NamespaceListView.as_view(),
|
regex=r'^namespaces/$', view=NamespaceListView.as_view(),
|
||||||
name='namespace_list'
|
name='namespace_list'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^namespace/(?P<namespace_name>\w+)/$',
|
regex=r'^namespaces/(?P<namespace_name>\w+)/$',
|
||||||
view=NamespaceDetailView.as_view(), name='namespace_detail'
|
view=NamespaceDetailView.as_view(), name='namespace_detail'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^edit/(?P<setting_global_name>\w+)/$',
|
regex=r'^namespaces/settings/(?P<setting_global_name>\w+)/edit/$',
|
||||||
view=SettingEditView.as_view(), name='setting_edit_view'
|
view=SettingEditView.as_view(), name='setting_edit_view'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user