Merge branch 'versions/minor' into features/workflow_email_action

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2019-07-13 02:56:02 -04:00
116 changed files with 3116 additions and 4521 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1 +1 @@
3.2.5 3.2.6

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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=_('Signatures'), name='django_gpg') namespace = Namespace(label=_('Signatures'), name='django_gpg')

View File

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

View File

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

View File

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

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

View 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. '
) )
) )

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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=_('Workflows'), name='document_states') namespace = Namespace(label=_('Workflows'), name='document_states')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1 @@
from .workflow_instance_views import * # NOQA
from .workflow_proxy_views import * # NOQA
from .workflow_views import * # NOQA

View File

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

View File

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

View File

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

View File

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

View 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

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

View 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(

View 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
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=_(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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'}
)

View 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.'
),
)

View File

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

View File

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