Compare commits
266 Commits
features/q
...
clients/bc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a99a9df3e | ||
|
|
f0ca92c06b | ||
|
|
69086d87dd | ||
|
|
89f05aeaa1 | ||
|
|
a966d6c8cf | ||
|
|
cf18e99caa | ||
|
|
f87454c0b6 | ||
|
|
b7febc8df5 | ||
|
|
f4b34bf48d | ||
|
|
d2fd865b68 | ||
|
|
a5e00ceba9 | ||
|
|
f09fec0aff | ||
|
|
ce7c805251 | ||
|
|
1bcc9332b2 | ||
|
|
ce4413d539 | ||
|
|
547c31d216 | ||
|
|
f4293a7c06 | ||
|
|
1779d482ac | ||
|
|
e0e4f238f6 | ||
|
|
fecfb37a84 | ||
|
|
3e2aaf391e | ||
|
|
230fde0ab2 | ||
|
|
d9865af200 | ||
|
|
72f8fcf720 | ||
|
|
30668d9d0b | ||
|
|
d5aab12b8d | ||
|
|
ebc0a5f449 | ||
|
|
415d3bcd2f | ||
|
|
b985f2ef05 | ||
|
|
15c953815e | ||
|
|
390e552c1f | ||
|
|
9041f00caa | ||
|
|
b0163319eb | ||
|
|
762cdc5b89 | ||
|
|
396cbb4b22 | ||
|
|
8b0cd93526 | ||
|
|
d190dbca03 | ||
|
|
4384452423 | ||
|
|
76fef4f247 | ||
|
|
f5d0e4d560 | ||
|
|
4b3ab82ee2 | ||
|
|
f8eda67bd5 | ||
|
|
58bcf20a46 | ||
|
|
49979dede5 | ||
| 09f481f5f0 | |||
|
|
a250919acc | ||
|
|
38980e5f75 | ||
|
|
6503d9474d | ||
|
|
e7734def58 | ||
|
|
f50d22b382 | ||
|
|
ad37228466 | ||
|
|
0917bd57b3 | ||
|
|
4dd270e75b | ||
|
|
3428c6aa20 | ||
|
|
eb1fb8511b | ||
|
|
bdbc7ef086 | ||
|
|
abea863184 | ||
|
|
d394583729 | ||
|
|
4db59c0808 | ||
|
|
12f24316a1 | ||
|
|
ef0843276b | ||
|
|
e20102333e | ||
|
|
4ecf075fd4 | ||
|
|
cc81a6905a | ||
|
|
3c9454160f | ||
|
|
2e1600c334 | ||
|
|
3e9f30cb91 | ||
|
|
a3a78f755d | ||
|
|
3988dedebf | ||
|
|
ff34c7d00a | ||
|
|
fe2de33e98 | ||
|
|
3efd1bd89d | ||
|
|
ea516cbc23 | ||
|
|
52ad3e7418 | ||
|
|
a001b4bbb3 | ||
|
|
31ed0e1ac8 | ||
|
|
9ad82695d9 | ||
|
|
69af4dd6b3 | ||
|
|
1c7ceca432 | ||
|
|
c05dcf5b05 | ||
|
|
85b05dd6ec | ||
|
|
9752584135 | ||
|
|
fd0d5728a1 | ||
|
|
88863fd6d0 | ||
|
|
3a7025d9c4 | ||
|
|
150c5d8cc2 | ||
|
|
93ba547350 | ||
|
|
f920dffc01 | ||
|
|
c2e99e6efb | ||
|
|
ff6674cc4a | ||
|
|
669dfeb30a | ||
|
|
6635bb4235 | ||
|
|
88bc29e4d7 | ||
|
|
9315776926 | ||
|
|
40a306996c | ||
|
|
033cecd946 | ||
|
|
ee63829e7f | ||
|
|
e4bc007bba | ||
|
|
84b329f661 | ||
|
|
4c73239dde | ||
|
|
2e12a6af41 | ||
|
|
3d7e6b6fbe | ||
|
|
6f907d156a | ||
|
|
fac77a2f73 | ||
|
|
0c3b6e5388 | ||
|
|
e652c7208c | ||
|
|
53928b2ab6 | ||
|
|
afc6b54520 | ||
|
|
070355033e | ||
|
|
0029d3074f | ||
|
|
4558894faf | ||
|
|
adeea6247f | ||
|
|
3563297d48 | ||
|
|
1e1b4dedf4 | ||
|
|
d65bbb718a | ||
|
|
5352c6ac6f | ||
|
|
cb7d5bf82a | ||
|
|
41a7d00e9e | ||
|
|
82d7339a64 | ||
|
|
e889021f43 | ||
|
|
d3a53fb53a | ||
|
|
b6565effb5 | ||
|
|
cf43ef2f73 | ||
|
|
6ca2845d19 | ||
|
|
ab601f9180 | ||
|
|
0b42567179 | ||
|
|
030ddd68e0 | ||
|
|
649571ebb1 | ||
|
|
b99bb88008 | ||
|
|
fd08a23339 | ||
|
|
917ec55ada | ||
|
|
ec4644b5c9 | ||
|
|
ff86c4c518 | ||
|
|
daebf2ddcc | ||
|
|
49a16acdf5 | ||
|
|
8c064c953a | ||
|
|
3c7a23a5a9 | ||
|
|
6bcf35bef5 | ||
|
|
7ef6102876 | ||
|
|
4363bba0fe | ||
|
|
e2f2181ebb | ||
|
|
d4f7e2cd16 | ||
|
|
058e36b4a9 | ||
|
|
1ddd5f26b1 | ||
|
|
44652d49fb | ||
|
|
119c1bde76 | ||
|
|
ed227b4111 | ||
|
|
c44090aca6 | ||
|
|
8a7da6a103 | ||
|
|
3e3b1f75a0 | ||
|
|
1ab7b7b9b1 | ||
|
|
3fab5c1427 | ||
|
|
516c3aeb2c | ||
|
|
3ac1000b46 | ||
|
|
4adeefc978 | ||
|
|
8bc4b6a95e | ||
|
|
37e85590e8 | ||
|
|
78a0189e1c | ||
|
|
91b0b2d9c3 | ||
|
|
8a54deba3d | ||
|
|
22da1e0a78 | ||
|
|
c9668d62e5 | ||
|
|
7a01a77c43 | ||
|
|
9564db398f | ||
|
|
7faa24eb7b | ||
|
|
51f278301b | ||
|
|
2cc35c3c61 | ||
|
|
8c73fda1ae | ||
|
|
8811c8269f | ||
|
|
f36f99c5fb | ||
|
|
0e972eff06 | ||
|
|
7913b5ddcc | ||
|
|
1c86ea5b5b | ||
|
|
ec6a3bd960 | ||
|
|
080553c797 | ||
|
|
08ee07e652 | ||
|
|
d7d77fcb55 | ||
|
|
bb5324ef50 | ||
|
|
4c212f6ea4 | ||
|
|
941356ed69 | ||
|
|
97804b255b | ||
|
|
06c3ef6583 | ||
|
|
6cd857e2bf | ||
|
|
fbb0f0b9bd | ||
|
|
9e068c3e83 | ||
|
|
72a3807354 | ||
|
|
109fcba795 | ||
|
|
01380e0572 | ||
|
|
5146c6d202 | ||
|
|
300bdbfc8a | ||
|
|
a0331e0236 | ||
|
|
e3267d3973 | ||
|
|
eb7cbc73ee | ||
|
|
596b5ccf67 | ||
|
|
34838a438d | ||
|
|
572690e2bc | ||
|
|
303e34299a | ||
|
|
c628de9ede | ||
|
|
e73be6bbab | ||
|
|
c9fd8b02e3 | ||
|
|
e1a63064dc | ||
|
|
42db8255d1 | ||
|
|
22ba6cfb49 | ||
|
|
02bba73ca7 | ||
|
|
d0daf559c7 | ||
|
|
f8f6700459 | ||
|
|
c318d37445 | ||
|
|
246fc15988 | ||
|
|
14d45cbe90 | ||
|
|
42a544c6e3 | ||
|
|
75be11bc96 | ||
|
|
ebf29d0eed | ||
|
|
a391d27b44 | ||
|
|
753c9b8b4b | ||
|
|
9cb6c6599d | ||
|
|
8bd0d0166b | ||
|
|
bee0c0b189 | ||
|
|
744bfefa5c | ||
|
|
850fb16c8c | ||
|
|
72ba805fbb | ||
|
|
3d7b40f029 | ||
|
|
2039a9f13b | ||
|
|
bb8f12dd7a | ||
|
|
40ab1f3665 | ||
|
|
fdef757fd0 | ||
|
|
3608ee1141 | ||
|
|
7fb3d61dff | ||
|
|
e9aa11673b | ||
|
|
03a7aa5daf | ||
|
|
755f20c5c4 | ||
|
|
64772e2e90 | ||
|
|
75a4a426e0 | ||
|
|
42a7ebeea2 | ||
|
|
3d22f48555 | ||
|
|
488e048d8f | ||
|
|
2f82559a5c | ||
|
|
7d5b7b9fc4 | ||
|
|
7aa68b8bbf | ||
|
|
aecde926f2 | ||
|
|
6b95628e56 | ||
|
|
56a1b97b46 | ||
|
|
34a5a54c8b | ||
|
|
0c17ab3f8a | ||
|
|
c967a25f82 | ||
|
|
7562588c42 | ||
|
|
a1a706b7b9 | ||
|
|
d623cb2df5 | ||
|
|
488ddcf1e1 | ||
|
|
3d39893f17 | ||
|
|
3694839d97 | ||
|
|
cce27aceca | ||
|
|
c73d251370 | ||
|
|
091f0d1cfd | ||
|
|
d2affdcf21 | ||
|
|
885d430b98 | ||
|
|
39eabe1c54 | ||
|
|
f6ad579829 | ||
|
|
6fc9e46882 | ||
|
|
2d326a679d | ||
|
|
aa8c2db446 | ||
|
|
925b55d76d | ||
|
|
5808d3653d | ||
|
|
bc072f7b7e | ||
|
|
b3d59eee39 | ||
|
|
7d379a52af | ||
|
|
499ab1f3e7 |
@@ -19,7 +19,7 @@ job_docker_build:
|
||||
- docker build --pull -t "$CI_REGISTRY_IMAGE" -f docker/Dockerfile .
|
||||
- VERSION=`cat docker/rootfs/version`
|
||||
- docker tag "$CI_REGISTRY_IMAGE" "$CI_REGISTRY_IMAGE:$VERSION"
|
||||
- docker run --rm "$CI_REGISTRY_IMAGE:$VERSION" run-tests
|
||||
- docker run --rm "$CI_REGISTRY_IMAGE:$VERSION" run_tests
|
||||
- docker push "$CI_REGISTRY_IMAGE:$VERSION"
|
||||
- docker push "$CI_REGISTRY_IMAGE:latest"
|
||||
- docker tag "$CI_REGISTRY_IMAGE:$VERSION" registry-1.docker.io/mayanedms/mayanedms:"$VERSION"
|
||||
@@ -58,11 +58,12 @@ job_docker_nightly:
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
script:
|
||||
- docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" -f docker/Dockerfile .
|
||||
- docker run --rm "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" run-tests
|
||||
- docker run --rm "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" run_tests
|
||||
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
|
||||
only:
|
||||
- nightly
|
||||
- staging
|
||||
- /^clients\/.+$/
|
||||
|
||||
job_documentation_build:
|
||||
stage: build_documentation
|
||||
@@ -152,7 +153,9 @@ job_push_python:
|
||||
- locale-gen en_US.UTF-8
|
||||
- update-locale LANG=en_US.UTF-8
|
||||
- export LC_ALL=en_US.UTF-8
|
||||
- apt-get install -qq curl exiftool gcc ghostscript gnupg1 graphviz libfuse2 libjpeg-dev libmagic1 libpng-dev libtiff-dev poppler-utils libreoffice poppler-utils python-dev python-pip tesseract-ocr tesseract-ocr-deu
|
||||
- apt-get install -qq curl exiftool gcc ghostscript gnupg1 graphviz libfuse2 libjpeg-dev libmagic1 libpng-dev libtiff-dev poppler-utils libreoffice poppler-utils python-dev python3-dev python-virtualenv tesseract-ocr tesseract-ocr-deu
|
||||
- virtualenv venv -p /usr/bin/python3
|
||||
- . venv/bin/activate
|
||||
- pip install -r requirements.txt -r requirements/testing-base.txt
|
||||
only:
|
||||
- releases/all
|
||||
@@ -160,6 +163,7 @@ job_push_python:
|
||||
- releases/python
|
||||
- staging
|
||||
- nightly
|
||||
- /^clients\/.+$/
|
||||
|
||||
test-mysql:
|
||||
<<: *test_base
|
||||
@@ -170,6 +174,7 @@ test-mysql:
|
||||
- mysql:8.0.3
|
||||
script:
|
||||
- apt-get install -qq libmysqlclient-dev mysql-client
|
||||
- . venv/bin/activate
|
||||
- pip install mysqlclient
|
||||
- mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p"$MYSQL_ENV_MYSQL_ROOT_PASSWORD" -e "set global character_set_server=utf8mb4;"
|
||||
- python manage.py test --mayan-apps --settings=mayan.settings.testing.gitlab-ci.db_mysql --nomigrations
|
||||
@@ -185,6 +190,7 @@ test-postgres:
|
||||
- postgres
|
||||
script:
|
||||
- apt-get install -qq libpq-dev
|
||||
- . venv/bin/activate
|
||||
- pip install psycopg2
|
||||
- python manage.py test --mayan-apps --settings=mayan.settings.testing.gitlab-ci.db_postgres --nomigrations
|
||||
tags:
|
||||
@@ -193,6 +199,7 @@ test-postgres:
|
||||
test-sqlite:
|
||||
<<: *test_base
|
||||
script:
|
||||
- . venv/bin/activate
|
||||
- python manage.py test --mayan-apps --settings=mayan.settings.testing.gitlab-ci --nomigrations
|
||||
|
||||
deploy_demo:
|
||||
|
||||
13
.tx/config
13
.tx/config
@@ -115,6 +115,12 @@ source_lang = en
|
||||
source_file = mayan/apps/events/locale/en/LC_MESSAGES/django.po
|
||||
type = PO
|
||||
|
||||
[mayan-edms.file_caching-3-0]
|
||||
file_filter = mayan/apps/file_caching/locale/<lang>/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
source_file = mayan/apps/file_caching/locale/en/LC_MESSAGES/django.po
|
||||
type = PO
|
||||
|
||||
[mayan-edms.file_metadata-3-0]
|
||||
file_filter = mayan/apps/file_metadata/locale/<lang>/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
@@ -222,3 +228,10 @@ file_filter = mayan/apps/user_management/locale/<lang>/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
source_file = mayan/apps/user_management/locale/en/LC_MESSAGES/django.po
|
||||
type = PO
|
||||
|
||||
[mayan-edms.weblink-3-0]
|
||||
file_filter = mayan/apps/weblinks/locale/<lang>/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
source_file = mayan/apps/weblinks/locale/en/LC_MESSAGES/django.po
|
||||
type = PO
|
||||
|
||||
|
||||
20
CHANGES_BC.rst
Normal file
20
CHANGES_BC.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
- Use Select2 widget for the document type selection form.
|
||||
- Update source column matching to be additive and not exclusive.
|
||||
- Add two columns to show the number of documents per workflow and
|
||||
workflow state.
|
||||
- Sort module.
|
||||
- Add link to sort individual indexes.
|
||||
- Support exclusions from source columns.
|
||||
- Improve source column exclusion. Improve for model subclasses in partial querysets.
|
||||
- Add sortable index instance label column.
|
||||
- Add rectangle drawing transformation.
|
||||
- Redactions app.
|
||||
- Remove duplicated trashed document preview.
|
||||
- Add label to trashed date and time document source column.
|
||||
- Tag created event fix.
|
||||
|
||||
3.2.3 (2019-06-21)
|
||||
* Add a reusable task to upload documents.
|
||||
* Add MVP of the importer app.
|
||||
|
||||
3.2.4-3.2.8 (2019-10-07)
|
||||
84
HISTORY.rst
84
HISTORY.rst
@@ -1,4 +1,85 @@
|
||||
3.2.8 (2019-XX-XX)
|
||||
3.3 (2019-XX-XX)
|
||||
================
|
||||
- Add support for icon shadows.
|
||||
- Add icons and no-result template to the object error log view and
|
||||
links.
|
||||
- Use Select2 widget for the document type selection form.
|
||||
- Backport the vertical main menu update.
|
||||
- Backport workflow preview refactor. GitLab issue #532.
|
||||
- Add support for source column inheritance.
|
||||
- Add support for source column exclusion.
|
||||
- Backport workflow context 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.
|
||||
- Backport file cache manager app.
|
||||
- Convert document image cache to use file cache manager app.
|
||||
Add setting DOCUMENTS_CACHE_MAXIMUM_SIZE defaults to 500 MB.
|
||||
- Replace djcelery and replace it with django-celery-beat.
|
||||
- Update Celery to version 4.3.0
|
||||
Thanks to Jakob Haufe (@sur5r) and Jesaja Everling (@jeverling)
|
||||
for much of the research and code updates.
|
||||
- Support wildcard MIME type associations for the file metadata drivers.
|
||||
- Rename MAYAN_GUID to MAYAN_GID
|
||||
- Update Gunicorn to use sync workers.
|
||||
- Include devpi-server as a development dependency.
|
||||
- Update default Docker stack file.
|
||||
- Remove Redis from the Docker image.
|
||||
- Add Celery flower to the Docker image.
|
||||
- Allow PIP proxying to the Docker image during build.
|
||||
- Default Celery worker concurrency to 0 (auto).
|
||||
- Set DJANGO_SETTINGS_MODULE environment variable to make it
|
||||
available to sub processes.
|
||||
- Add entrypoint commands to run single workers, single gunicorn
|
||||
or single celery commands like "flower".
|
||||
- Add platform template to return queues for a worker.
|
||||
- Update the EXIFTOOL driver to run for all documents
|
||||
regardless of MIME type.
|
||||
- Remove task inspection from task manager app.
|
||||
- Move pagination navigation inside the toolbar.
|
||||
- Remove document image clear link and view.
|
||||
This is now handled by the file caching app.
|
||||
- Add web links app.
|
||||
- Add support to display column help text
|
||||
as a tooltip.
|
||||
- Update numeric dashboard widget to display
|
||||
thousand commas.
|
||||
- Add support for disabling document pages.
|
||||
- Add support for converter layers.
|
||||
- Add redactions app.
|
||||
- Unify all line endings to be Linux style.
|
||||
- Add support for changing the system messages position.
|
||||
GitLab issue #640. Thanks to Matthias Urhahn (@d4rken).
|
||||
- Update Docker deploy script. Use alpine postgres version.
|
||||
Support Docker networks and make it the default.
|
||||
Delete the containers to allow the script to be idempotent.
|
||||
Deploy a Redis container.
|
||||
|
||||
3.2.8 (2019-10-01)
|
||||
==================
|
||||
- Fix error when accessing some API entry points without
|
||||
being authenticated.
|
||||
@@ -17,6 +98,7 @@
|
||||
of selection of documents.
|
||||
- Add parsed content deleted event.
|
||||
- Allow scaling of UI on mobile devices.
|
||||
- Add Chinese fonts to the Docker image
|
||||
|
||||
3.2.7 (2019-08-28)
|
||||
==================
|
||||
|
||||
41
Makefile
41
Makefile
@@ -1,5 +1,15 @@
|
||||
.PHONY: clean-pyc clean-build
|
||||
|
||||
DOCKER_MYSQL_IMAGE = mysql:8.0
|
||||
DOCKER_ORACLE_IMAGE = wnameless/oracle-xe-11g
|
||||
DOCKER_POSTGRES_IMAGE = postgres:9.6-alpine
|
||||
DOCKER_REDIS_IMAGE = redis:5.0-alpine
|
||||
|
||||
PYTHON_MYSQL_VERSION = 1.4.4
|
||||
PYTHON_PSYCOPG2_VERSION = 2.8.3
|
||||
PYTHON_RABBITMQ_VERSION = 2.0.0
|
||||
PYTHON_REDIS_VERSION = 3.2.1
|
||||
|
||||
help:
|
||||
@echo "Usage: make <target>\n"
|
||||
@awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_-]+:.*?## / { printf " * %-40s -%s\n", $$1, $$2 }' $(MAKEFILE_LIST)|sort
|
||||
@@ -18,7 +28,7 @@ clean-pyc: ## Remove Python artifacts.
|
||||
find . -name '*.pyc' -exec rm -f {} +
|
||||
find . -name '*.pyo' -exec rm -f {} +
|
||||
find . -name '*~' -exec rm -f {} +
|
||||
|
||||
find . -name '__pycache__' -exec rm -R -f {} +
|
||||
|
||||
# Testing
|
||||
|
||||
@@ -33,10 +43,10 @@ test-all: clean-pyc
|
||||
test-launch-postgres:
|
||||
@docker rm -f test-postgres || true
|
||||
@docker volume rm test-postgres || true
|
||||
docker run -d --name test-postgres -p 5432:5432 -v test-postgres:/var/lib/postgresql/data healthcheck/postgres
|
||||
docker run -d --name test-postgres -p 5432:5432 -v test-postgres:/var/lib/postgresql/data $(DOCKER_POSTGRES_IMAGE)
|
||||
sudo apt-get install -q libpq-dev
|
||||
pip install psycopg2
|
||||
while ! docker inspect --format='{{json .State.Health}}' test-postgres|grep 'Status":"healthy"'; do sleep 1; done
|
||||
pip install psycopg2==$(PYTHON_PSYCOPG2_VERSION)
|
||||
while ! nc -z 127.0.0.1 5432; do sleep 1; done
|
||||
|
||||
test-with-postgres: ## MODULE=<python module name> - Run tests for a single app, module or test class against a Postgres database container.
|
||||
test-with-postgres: test-launch-postgres
|
||||
@@ -53,10 +63,10 @@ test-with-postgres-all: test-launch-postgres
|
||||
test-launch-mysql:
|
||||
@docker rm -f test-mysql || true
|
||||
@docker volume rm test-mysql || true
|
||||
docker run -d --name test-mysql -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=True -e MYSQL_DATABASE=mayan -v test-mysql:/var/lib/mysql healthcheck/mysql
|
||||
docker run -d --name test-mysql -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=True -e MYSQL_DATABASE=mayan -v test-mysql:/var/lib/mysql $(DOCKER_MYSQL_IMAGE)
|
||||
sudo apt-get install -q libmysqlclient-dev mysql-client
|
||||
pip install mysqlclient
|
||||
while ! docker inspect --format='{{json .State.Health}}' test-mysql|grep 'Status":"healthy"'; do sleep 1; done
|
||||
pip install mysqlclient==$(PYTHON_MYSQL_VERSION)
|
||||
while ! nc -z 127.0.0.1 3306; do sleep 1; done
|
||||
mysql -h 127.0.0.1 -P 3306 -uroot -e "set global character_set_server=utf8mb4;"
|
||||
|
||||
test-with-mysql: ## MODULE=<python module name> - Run tests for a single app, module or test class against a MySQL database container.
|
||||
@@ -75,7 +85,7 @@ test-with-mysql-all: test-launch-mysql
|
||||
test-launch-oracle:
|
||||
@docker rm -f test-oracle || true
|
||||
@docker volume rm test-oracle || true
|
||||
docker run -d --name test-oracle -p 49160:22 -p 49161:1521 -e ORACLE_ALLOW_REMOTE=true -v test-oracle:/u01/app/oracle wnameless/oracle-xe-11g
|
||||
docker run -d --name test-oracle -p 49160:22 -p 49161:1521 -e ORACLE_ALLOW_REMOTE=true -v test-oracle:/u01/app/oracle $(DOCKER_ORACLE_IMAGE)
|
||||
# https://gist.github.com/kimus/10012910
|
||||
pip install cx_Oracle
|
||||
while ! nc -z 127.0.0.1 49161; do sleep 1; done
|
||||
@@ -234,20 +244,21 @@ generate-requirements: ## Generate all requirements files from the project deped
|
||||
# Dev server
|
||||
|
||||
runserver: ## Run the development server.
|
||||
./manage.py runserver --settings=mayan.settings.development $(ADDRPORT)
|
||||
./manage.py runserver --nothreading --settings=mayan.settings.development $(ADDRPORT)
|
||||
|
||||
runserver_plus: ## Run the Django extension's development server.
|
||||
./manage.py runserver_plus --settings=mayan.settings.development $(ADDRPORT)
|
||||
./manage.py runserver_plus --nothreading --settings=mayan.settings.development $(ADDRPORT)
|
||||
|
||||
shell_plus: ## Run the shell_plus command.
|
||||
./manage.py shell_plus --settings=mayan.settings.development
|
||||
|
||||
test-with-docker-services-on: ## Launch and initialize production-like services using Docker (Postgres and Redis).
|
||||
docker run -d --name redis -p 6379:6379 redis
|
||||
docker run -d --name postgres -p 5432:5432 postgres
|
||||
docker run -d --name redis -p 6379:6379 $(DOCKER_REDIS_IMAGE)
|
||||
docker run -d --name postgres -p 5432:5432 $(DOCKER_POSTGRES_IMAGE)
|
||||
while ! nc -z 127.0.0.1 6379; do sleep 1; done
|
||||
while ! nc -z 127.0.0.1 5432; do sleep 1; done
|
||||
sleep 4
|
||||
pip install psycopg2==$(PYTHON_PSYCOPG2_VERSION) redis==$(PYTHON_REDIS_VERSION)
|
||||
./manage.py initialsetup --settings=mayan.settings.staging.docker
|
||||
|
||||
test-with-docker-services-off: ## Stop and delete the Docker production-like services.
|
||||
@@ -258,10 +269,10 @@ test-with-docker-frontend: ## Launch a front end instance that uses the producti
|
||||
./manage.py runserver --settings=mayan.settings.staging.docker
|
||||
|
||||
test-with-docker-worker: ## Launch a worker instance that uses the production-like services.
|
||||
./manage.py celery worker --settings=mayan.settings.staging.docker -B -l INFO -O fair
|
||||
DJANGO_SETTINGS_MODULE=mayan.settings.staging.docker ./manage.py celery worker -A mayan -B -l INFO -O fair
|
||||
|
||||
docker-mysql-on: ## Launch and initialize a MySQL Docker container.
|
||||
docker run -d --name mysql -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=True -e MYSQL_DATABASE=mayan_edms mysql
|
||||
docker run -d --name mysql -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=True -e MYSQL_DATABASE=mayan_edms $(DOCKER_MYSQL_IMAGE)
|
||||
while ! nc -z 127.0.0.1 3306; do sleep 1; done
|
||||
|
||||
docker-mysql-off: ## Stop and delete the MySQL Docker container.
|
||||
@@ -269,7 +280,7 @@ docker-mysql-off: ## Stop and delete the MySQL Docker container.
|
||||
docker rm mysql
|
||||
|
||||
docker-postgres-on: ## Launch and initialize a PostgreSQL Docker container.
|
||||
docker run -d --name postgres -p 5432:5432 postgres
|
||||
docker run -d --name postgres -p 5432:5432 $(DOCKER_POSTGRES_IMAGE)
|
||||
while ! nc -z 127.0.0.1 5432; do sleep 1; done
|
||||
|
||||
docker-postgres-off: ## Stop and delete the PostgreSQL Docker container.
|
||||
|
||||
@@ -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
@@ -5,21 +5,25 @@ set -e
|
||||
# $ curl -fsSL get.mayan-edms.com -o get-mayan-edms.sh
|
||||
# $ sh get-mayan-edms.sh
|
||||
#
|
||||
# NOTE: Make sure to verify the contents of the script
|
||||
# NOTE: Before executing, make sure to verify the contents of the script
|
||||
# you downloaded matches the contents of docker.sh
|
||||
# located at https://gitlab.com/mayan-edms/mayan-edms/blob/master/contrib/scripts/install/docker.sh
|
||||
# before executing.
|
||||
|
||||
: ${VERBOSE:=true}
|
||||
: ${INSTALL_DOCKER:=false}
|
||||
: ${DELETE_VOLUMES:=false}
|
||||
: ${USE_DOCKER_NETWORK:=true}
|
||||
: ${DOCKER_NETWORK_NAME:=mayan}
|
||||
: ${DATABASE_USER:=mayan}
|
||||
: ${DATABASE_NAME:=mayan}
|
||||
: ${DATABASE_PASSWORD:=mayanuserpass}
|
||||
: ${DOCKER_POSTGRES_IMAGE:=postgres:9.6}
|
||||
: ${DOCKER_POSTGRES_IMAGE:=postgres:9.6-alpine}
|
||||
: ${DOCKER_POSTGRES_CONTAINER:=mayan-edms-postgres}
|
||||
: ${DOCKER_POSTGRES_VOLUME:=/docker-volumes/mayan-edms/postgres}
|
||||
: ${DOCKER_POSTGRES_PORT:=5432}
|
||||
: ${DOCKER_REDIS_IMAGE:=redis:5.0-alpine}
|
||||
: ${DOCKER_REDIS_CONTAINER:=mayan-edms-redis}
|
||||
: ${DOCKER_REDIS_PORT:=6379}
|
||||
: ${DOCKER_MAYAN_IMAGE:=mayanedms/mayanedms:latest}
|
||||
: ${DOCKER_MAYAN_CONTAINER:=mayan-edms}
|
||||
: ${DOCKER_MAYAN_VOLUME:=/docker-volumes/mayan-edms/media}
|
||||
@@ -44,6 +48,8 @@ echo "Variable values to be used:"
|
||||
echo "---------------------------"
|
||||
echo "INSTALL_DOCKER: $INSTALL_DOCKER"
|
||||
echo "DELETE_VOLUMES: $DELETE_VOLUMES"
|
||||
echo "USE_DOCKER_NETWORK: $USE_DOCKER_NETWORK"
|
||||
echo "DOCKER_NETWORK_NAME: $DOCKER_NETWORK_NAME"
|
||||
echo "DATABASE_USER: $DATABASE_USER"
|
||||
echo "DATABASE_NAME: $DATABASE_NAME"
|
||||
echo "DATABASE_PASSWORD: $DATABASE_PASSWORD"
|
||||
@@ -51,10 +57,17 @@ echo "DOCKER_POSTGRES_IMAGE: $DOCKER_POSTGRES_IMAGE"
|
||||
echo "DOCKER_POSTGRES_CONTAINER: $DOCKER_POSTGRES_CONTAINER"
|
||||
echo "DOCKER_POSTGRES_VOLUME: $DOCKER_POSTGRES_VOLUME"
|
||||
echo "DOCKER_POSTGRES_PORT: $DOCKER_POSTGRES_PORT"
|
||||
echo "DOCKER_REDIS_IMAGE: $DOCKER_REDIS_IMAGE"
|
||||
echo "DOCKER_REDIS_CONTAINER: $DOCKER_REDIS_CONTAINER"
|
||||
echo "DOCKER_REDIS_PORT: $DOCKER_REDIS_PORT"
|
||||
echo "DOCKER_MAYAN_IMAGE: $DOCKER_MAYAN_IMAGE"
|
||||
echo "DOCKER_MAYAN_CONTAINER: $DOCKER_MAYAN_CONTAINER"
|
||||
echo "DOCKER_MAYAN_VOLUME: $DOCKER_MAYAN_VOLUME"
|
||||
echo "\nStarting in 10 seconds."
|
||||
echo
|
||||
echo "Override any of them by setting them before the script. "
|
||||
echo "Example: INSTALL_DOCKER=true sh get-mayan-edms.sh"
|
||||
|
||||
echo "\nStarting in 10 seconds. Press CTRL+C to cancel."
|
||||
sleep 10
|
||||
fi
|
||||
|
||||
@@ -72,33 +85,62 @@ if [ -z `which docker` ]; then
|
||||
fi
|
||||
|
||||
echo -n "* Removing existing Mayan EDMS and PostgreSQL containers (no data will be lost)..."
|
||||
true || docker stop $DOCKER_MAYAN_CONTAINER >/dev/null 2>&1
|
||||
true || docker rm $DOCKER_MAYAN_CONTAINER >/dev/null 2>&1
|
||||
true || docker stop $DOCKER_POSTGRES_CONTAINER >/dev/null 2>&1
|
||||
true || docker rm $DOCKER_POSTGRES_CONTAINER >/dev/null 2>&1
|
||||
docker rm -f $DOCKER_REDIS_CONTAINER >/dev/null 2>&1 || true
|
||||
docker rm -f $DOCKER_POSTGRES_CONTAINER >/dev/null 2>&1 || true
|
||||
docker rm -f $DOCKER_MAYAN_CONTAINER >/dev/null 2>&1 || true
|
||||
echo "Done"
|
||||
|
||||
if [ "$DELETE_VOLUMES" = true ]; then
|
||||
echo -n "* Deleting Docker volumes in 5 seconds (warning: this delete all document data)..."
|
||||
echo -n "* Deleting Docker volumes in 5 seconds (warning: this will delete all document data). Press CTRL+C to cancel..."
|
||||
sleep 5
|
||||
true || rm DOCKER_MAYAN_VOLUME -Rf
|
||||
true || rm DOCKER_POSTGRES_VOLUME -Rf
|
||||
rm DOCKER_MAYAN_VOLUME -Rf || true
|
||||
rm DOCKER_POSTGRES_VOLUME -Rf || true
|
||||
echo "Done"
|
||||
fi
|
||||
|
||||
echo -n "* Pulling (downloading) the Mayan EDMS Docker image..."
|
||||
docker pull $DOCKER_MAYAN_IMAGE >/dev/null
|
||||
echo -n "* Pulling (downloading) the Redis Docker image..."
|
||||
docker pull $DOCKER_REDIS_IMAGE > /dev/null
|
||||
echo "Done"
|
||||
|
||||
echo -n "* Pulling (downloading) the PostgreSQL Docker image..."
|
||||
docker pull $DOCKER_POSTGRES_IMAGE > /dev/null
|
||||
echo "Done"
|
||||
|
||||
echo -n "* Pulling (downloading) the Mayan EDMS Docker image..."
|
||||
docker pull $DOCKER_MAYAN_IMAGE >/dev/null
|
||||
echo "Done"
|
||||
|
||||
if [ "$USE_DOCKER_NETWORK" = true ]; then
|
||||
echo -n "* Creating Docker network..."
|
||||
docker network create $DOCKER_NETWORK_NAME 2> /dev/null || true
|
||||
# Ignore error if the network already exists
|
||||
echo "Done"
|
||||
fi
|
||||
|
||||
if [ "$USE_DOCKER_NETWORK" = true ]; then
|
||||
NETWORK_ARGUMENT="--network=$DOCKER_NETWORK_NAME"
|
||||
POSTGRES_PORT_ARGUMENT=""
|
||||
REDIS_PORT_ARGUMENT=""
|
||||
MAYAN_DATABASE_PORT_ARGUMENT=""
|
||||
MAYAN_DATABASE_HOST_ARGUMENT="-e MAYAN_DATABASE_HOST=$DOCKER_POSTGRES_CONTAINER"
|
||||
MAYAN_BROKER_URL_ARGUMENT="-e MAYAN_BROKER_URL=redis://$DOCKER_REDIS_CONTAINER:6379/0"
|
||||
MAYAN_CELERY_RESULT_BACKEND_ARGUMENT="-e MAYAN_CELERY_RESULT_BACKEND=redis://$DOCKER_REDIS_CONTAINER:6379/1"
|
||||
else
|
||||
NETWORK_ARGUMENT=""
|
||||
POSTGRES_PORT_ARGUMENT="-e $DOCKER_POSTGRES_PORT:5432"
|
||||
REDIS_PORT_ARGUMENT="-e $DOCKER_REDIS_PORT:6379"
|
||||
MAYAN_DATABASE_PORT_ARGUMENT="-e MAYAN_DATABASE_PORT=$DOCKER_POSTGRES_PORT"
|
||||
MAYAN_DATABASE_HOST_ARGUMENT="-e MAYAN_DATABASE_HOST=172.17.0.1"
|
||||
MAYAN_BROKER_URL_ARGUMENT="-e MAYAN_BROKER_URL=redis://172.17.0.1:6379/0"
|
||||
MAYAN_CELERY_RESULT_BACKEND_ARGUMENT="-e MAYAN_CELERY_RESULT_BACKEND=redis://172.17.0.1:6379/1"
|
||||
fi
|
||||
|
||||
echo -n "* Deploying the PostgreSQL container..."
|
||||
docker run -d \
|
||||
--name $DOCKER_POSTGRES_CONTAINER \
|
||||
$NETWORK_ARGUMENT \
|
||||
--restart=always \
|
||||
-p $DOCKER_POSTGRES_PORT:5432 \
|
||||
$POSTGRES_PORT_ARGUMENT \
|
||||
-e POSTGRES_USER=$DATABASE_USER \
|
||||
-e POSTGRES_DB=$DATABASE_NAME \
|
||||
-e POSTGRES_PASSWORD=$DATABASE_PASSWORD \
|
||||
@@ -106,6 +148,24 @@ docker run -d \
|
||||
$DOCKER_POSTGRES_IMAGE >/dev/null
|
||||
echo "Done"
|
||||
|
||||
echo -n "* Deploying the Redis container..."
|
||||
docker run -d \
|
||||
--name $DOCKER_REDIS_CONTAINER \
|
||||
$NETWORK_ARGUMENT \
|
||||
--restart=always \
|
||||
$REDIS_PORT_ARGUMENT \
|
||||
$DOCKER_REDIS_IMAGE \
|
||||
redis-server \
|
||||
--appendonly no \
|
||||
--databases 2 \
|
||||
--maxmemory 100mb \
|
||||
--maxmemory-policy allkeys-lru \
|
||||
--maxclients 500 \
|
||||
--save "" \
|
||||
--tcp-backlog 256 \
|
||||
>/dev/null
|
||||
echo "Done"
|
||||
|
||||
echo -n "* Waiting for the PostgreSQL container to be ready (10 seconds)..."
|
||||
sleep 10
|
||||
echo "Done"
|
||||
@@ -113,15 +173,18 @@ echo "Done"
|
||||
echo -n "* Deploying Mayan EDMS container..."
|
||||
docker run -d \
|
||||
--name $DOCKER_MAYAN_CONTAINER \
|
||||
$NETWORK_ARGUMENT \
|
||||
--restart=always \
|
||||
-p 80:8000 \
|
||||
-e MAYAN_DATABASE_ENGINE=django.db.backends.postgresql \
|
||||
-e MAYAN_DATABASE_HOST=172.17.0.1 \
|
||||
$MAYAN_DATABASE_HOST_ARGUMENT \
|
||||
$MAYAN_DATABASE_PORT_ARGUMENT \
|
||||
-e MAYAN_DATABASE_NAME=$DATABASE_NAME \
|
||||
-e MAYAN_DATABASE_PASSWORD=$DATABASE_PASSWORD \
|
||||
-e MAYAN_DATABASE_USER=$DATABASE_USER \
|
||||
-e MAYAN_DATABASE_PORT=$DOCKER_POSTGRES_PORT \
|
||||
-e MAYAN_DATABASE_CONN_MAX_AGE=0 \
|
||||
$MAYAN_BROKER_URL_ARGUMENT \
|
||||
$MAYAN_CELERY_RESULT_BACKEND_ARGUMENT \
|
||||
-v $DOCKER_MAYAN_VOLUME:/var/lib/mayan \
|
||||
$DOCKER_MAYAN_IMAGE >/dev/null
|
||||
echo "Done"
|
||||
|
||||
@@ -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
|
||||
@@ -13,11 +13,12 @@ APP_LIST = (
|
||||
'checkouts', 'common', 'converter', 'dashboards', 'dependencies',
|
||||
'django_gpg', 'document_comments', 'document_indexing',
|
||||
'document_parsing', 'document_signatures', 'document_states',
|
||||
'documents', 'dynamic_search', 'events', 'file_metadata', 'linking',
|
||||
'lock_manager', 'mayan_statistics', 'mailer', 'metadata', 'mirroring',
|
||||
'motd', 'navigation', 'ocr', 'permissions', 'platform', 'rest_api',
|
||||
'smart_settings', 'sources', 'storage', 'tags', 'task_manager',
|
||||
'user_management'
|
||||
'documents', 'dynamic_search', 'events', 'file_caching',
|
||||
'file_metadata', 'linking', 'lock_manager', 'mailer',
|
||||
'mayan_statistics', 'metadata', 'mirroring', 'motd', 'navigation',
|
||||
'ocr', 'permissions', 'platform', 'rest_api', 'smart_settings',
|
||||
'sources', 'storage', 'tags', 'task_manager', 'user_management',
|
||||
'weblinks'
|
||||
)
|
||||
|
||||
LANGUAGE_LIST = (
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# BASE_IMAGE - Bare bones image with the base packages needed to run Mayan EDMS
|
||||
####
|
||||
|
||||
FROM debian:9.8-slim as BASE_IMAGE
|
||||
FROM debian:10.0-slim as BASE_IMAGE
|
||||
|
||||
LABEL maintainer="Roberto Rosario roberto.rosario@mayan-edms.com"
|
||||
|
||||
@@ -22,18 +22,21 @@ RUN set -x \
|
||||
&& DEBIAN_FRONTEND=noninteractive \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
exiftool \
|
||||
fonts-arphic-uming \
|
||||
fonts-arphic-ukai \
|
||||
ghostscript \
|
||||
gpgv \
|
||||
gnupg1 \
|
||||
graphviz \
|
||||
libfuse2 \
|
||||
libmagic1 \
|
||||
libmariadbclient18 \
|
||||
libmariadb3 \
|
||||
libreoffice \
|
||||
libpq5 \
|
||||
poppler-utils \
|
||||
redis-server \
|
||||
python3-distutils \
|
||||
sane-utils \
|
||||
sudo \
|
||||
supervisor \
|
||||
@@ -52,22 +55,20 @@ apt-get update \
|
||||
&& if [ "$(uname -m)" = "armv7l" ]; then \
|
||||
ln -s /usr/lib/arm-linux-gnueabihf/libz.so /usr/lib/ \
|
||||
&& ln -s /usr/lib/arm-linux-gnueabihf/libjpeg.so /usr/lib/ \
|
||||
; fi \
|
||||
# Discard data when Redis runs out of memory
|
||||
&& echo "maxmemory-policy allkeys-lru" >> /etc/redis/redis.conf \
|
||||
# Disable saving the Redis database
|
||||
echo "save \"\"" >> /etc/redis/redis.conf \
|
||||
# Only provision 1 database
|
||||
&& echo "databases 1" >> /etc/redis/redis.conf
|
||||
|
||||
; fi
|
||||
|
||||
####
|
||||
# BUILDER_IMAGE - This image buildS the Python package and is discarded afterwards
|
||||
# BUILDER_IMAGE - This image builds the Python package and is discarded afterwards
|
||||
# only the build artifact is carried over to the next image.
|
||||
####
|
||||
|
||||
# Reuse image
|
||||
FROM BASE_IMAGE as BUILDER_IMAGE
|
||||
|
||||
# Python libraries caching
|
||||
ARG PIP_INDEX_URL
|
||||
ARG PIP_TRUSTED_HOST
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Copy the source files needed to build the Python package
|
||||
@@ -96,39 +97,40 @@ apt-get install -y --no-install-recommends \
|
||||
libssl-dev \
|
||||
g++ \
|
||||
gcc \
|
||||
python-dev \
|
||||
python-virtualenv \
|
||||
python3-dev \
|
||||
python3-venv \
|
||||
&& mkdir -p "${PROJECT_INSTALL_DIR}" \
|
||||
&& chown -R mayan:mayan "${PROJECT_INSTALL_DIR}" \
|
||||
&& chown -R mayan:mayan /src
|
||||
|
||||
USER mayan
|
||||
RUN python -m virtualenv "${PROJECT_INSTALL_DIR}" \
|
||||
RUN python3 -m venv "${PROJECT_INSTALL_DIR}" \
|
||||
&& . "${PROJECT_INSTALL_DIR}/bin/activate" \
|
||||
&& pip install --no-cache-dir --no-use-pep517 \
|
||||
librabbitmq==1.6.1 \
|
||||
mysql-python==1.2.5 \
|
||||
psycopg2==2.7.3.2 \
|
||||
redis==2.10.6 \
|
||||
&& pip install --no-cache-dir \
|
||||
librabbitmq==2.0.0 \
|
||||
mysqlclient==1.4.2.post1 \
|
||||
psycopg2==2.8.3 \
|
||||
redis==3.2.1 \
|
||||
flower==0.9.3 \
|
||||
# psutil is needed by ARM builds otherwise gevent and gunicorn fail to start
|
||||
&& UNAME=`uname -m` && if [ "${UNAME#*arm}" != $UNAME ]; then \
|
||||
pip install --no-cache-dir --no-use-pep517 \
|
||||
pip install --no-cache-dir \
|
||||
psutil==5.6.2 \
|
||||
; fi \
|
||||
# Install the Python packages needed to build Mayan EDMS
|
||||
&& pip install --no-cache-dir --no-use-pep517 -r /src/requirements/build.txt \
|
||||
&& pip install --no-cache-dir -r /src/requirements/build.txt \
|
||||
# Build Mayan EDMS
|
||||
&& python setup.py sdist \
|
||||
&& python3 setup.py sdist \
|
||||
# Install the built Mayan EDMS package
|
||||
&& pip install --no-cache-dir --no-use-pep517 dist/mayan* \
|
||||
&& pip install --no-cache-dir dist/mayan* \
|
||||
# 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
|
||||
|
||||
COPY --chown=mayan:mayan requirements/testing-base.txt "${PROJECT_INSTALL_DIR}"
|
||||
|
||||
####
|
||||
# Final image - BASE_IMAGE + Mayan install directory from the builder image
|
||||
# Final image - BASE_IMAGE + BUILDER_IMAGE artifact (Mayan install directory)
|
||||
####
|
||||
|
||||
FROM BASE_IMAGE
|
||||
@@ -144,7 +146,7 @@ VOLUME ["/var/lib/mayan"]
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
||||
|
||||
EXPOSE 8000
|
||||
CMD ["mayan"]
|
||||
CMD ["run_all"]
|
||||
|
||||
RUN ${PROJECT_INSTALL_DIR}/bin/mayan-edms.py platformtemplate supervisord_docker > /etc/supervisor/conf.d/mayan.conf \
|
||||
&& apt-get clean autoclean \
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
APT_PROXY ?= `/sbin/ip route|awk '/docker0/ { print $$9 }'`:3142
|
||||
HOST_IP = `/sbin/ip route|awk '/docker0/ { print $$9 }'`
|
||||
|
||||
APT_PROXY ?= $(HOST_IP):3142
|
||||
PIP_INDEX_URL ?= http://$(HOST_IP):3141/root/pypi/+simple/
|
||||
PIP_TRUSTED_HOST ?= $(HOST_IP)
|
||||
|
||||
IMAGE_VERSION ?= `cat docker/rootfs/version`
|
||||
CONSOLE_COLUMNS ?= `echo $$(tput cols)`
|
||||
CONSOLE_LINES ?= `echo $$(tput lines)`
|
||||
@@ -7,7 +12,7 @@ docker-build: ## Build a new image locally.
|
||||
docker build -t mayanedms/mayanedms:$(IMAGE_VERSION) -f docker/Dockerfile .
|
||||
|
||||
docker-build-with-proxy: ## Build a new image locally using an APT proxy as APT_PROXY.
|
||||
docker build -t mayanedms/mayanedms:$(IMAGE_VERSION) -f docker/Dockerfile --build-arg APT_PROXY=$(APT_PROXY) .
|
||||
docker build -t mayanedms/mayanedms:$(IMAGE_VERSION) -f docker/Dockerfile --build-arg APT_PROXY=$(APT_PROXY) --build-arg PIP_INDEX_URL=$(PIP_INDEX_URL) --build-arg PIP_TRUSTED_HOST=$(PIP_TRUSTED_HOST) --build-arg HTTP_PROXY=$(HTTP_PROXY) --build-arg HTTPS_PROXY=$(HTTPS_PROXY) .
|
||||
|
||||
docker-shell: ## Launch a bash instance inside a running container. Pass the container name via DOCKER_CONTAINER.
|
||||
docker exec -e TERM=$(TERM) -e "COLUMNS=$(CONSOLE_COLUMNS)" -e "LINES=$(CONSOLE_LINES)" -it $(DOCKER_CONTAINER) /bin/bash
|
||||
@@ -23,3 +28,13 @@ docker-test-cleanup: ## Delete the test container and the test volume.
|
||||
docker-test-all: ## Build and executed the test suite in a test container.
|
||||
docker-test-all: docker-build-with-proxy
|
||||
docker run --rm run-tests
|
||||
|
||||
docker-compose-build:
|
||||
docker-compose -f docker/docker-compose.yml -p mayan-edms build
|
||||
|
||||
docker-compose-build-with-proxy:
|
||||
docker-compose -f docker/docker-compose.yml -p mayan-edms build --build-arg APT_PROXY=$(APT_PROXY) --build-arg PIP_INDEX_URL=$(PIP_INDEX_URL) --build-arg PIP_TRUSTED_HOST=$(PIP_TRUSTED_HOST) --build-arg HTTP_PROXY=$(HTTP_PROXY) --build-arg HTTPS_PROXY=$(HTTPS_PROXY)
|
||||
|
||||
docker-compose-up:
|
||||
docker-compose -f docker/docker-compose.yml -p mayan-edms up
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
version: '2.1'
|
||||
|
||||
volumes:
|
||||
broker:
|
||||
driver: local
|
||||
app:
|
||||
driver: local
|
||||
db:
|
||||
driver: local
|
||||
results:
|
||||
driver: local
|
||||
|
||||
services:
|
||||
broker:
|
||||
container_name: mayan-edms-broker
|
||||
image: healthcheck/rabbitmq
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: mayan
|
||||
RABBITMQ_DEFAULT_PASS: mayan
|
||||
RABBITMQ_DEFAULT_VHOST: mayan
|
||||
volumes:
|
||||
- broker:/var/lib/rabbitmq
|
||||
results:
|
||||
container_name: mayan-edms-results
|
||||
image: healthcheck/redis
|
||||
volumes:
|
||||
- results:/data
|
||||
#db:
|
||||
# container_name: mayan-edms-db
|
||||
# image: healthcheck/mysql
|
||||
# environment:
|
||||
# MYSQL_DATABASE: mayan
|
||||
# MYSQL_PASSWORD: mayan-password
|
||||
# MYSQL_ROOT_PASSWORD: root-password
|
||||
# MYSQL_USER: mayan
|
||||
# volumes:
|
||||
# - db:/var/lib/mysql
|
||||
db:
|
||||
container_name: mayan-edms-db
|
||||
image: healthcheck/postgres
|
||||
environment:
|
||||
POSTGRES_DB: mayan
|
||||
POSTGRES_PASSWORD: mayan-password
|
||||
POSTGRES_USER: mayan
|
||||
volumes:
|
||||
- db:/var/lib/postgresql/data
|
||||
mayan-edms:
|
||||
container_name: mayan-edms-app
|
||||
image: mayan-edms/next
|
||||
build:
|
||||
context: ./
|
||||
args:
|
||||
- APT_PROXY=172.18.0.1:3142
|
||||
depends_on:
|
||||
broker:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
results:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MAYAN_BROKER_URL: amqp://mayan:mayan@broker:5672/mayan
|
||||
MAYAN_CELERY_RESULT_BACKEND: redis://results:6379/0
|
||||
MAYAN_DATABASE_ENGINE: django.db.backends.postgresql
|
||||
MAYAN_DATABASE_HOST: db
|
||||
MAYAN_DATABASE_NAME: mayan
|
||||
MAYAN_DATABASE_PASSWORD: mayan-password
|
||||
MAYAN_DATABASE_USER: mayan
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- app:/var/lib/mayan
|
||||
@@ -1,58 +1,130 @@
|
||||
version: '2.1'
|
||||
version: '3.7'
|
||||
|
||||
volumes:
|
||||
broker:
|
||||
driver: local
|
||||
app:
|
||||
driver: local
|
||||
db:
|
||||
driver: local
|
||||
results:
|
||||
driver: local
|
||||
networks:
|
||||
mayan-bridge:
|
||||
driver: bridge
|
||||
|
||||
services:
|
||||
broker:
|
||||
container_name: mayan-edms-broker
|
||||
image: healthcheck/rabbitmq
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: mayan
|
||||
RABBITMQ_DEFAULT_PASS: mayan
|
||||
RABBITMQ_DEFAULT_VHOST: mayan
|
||||
volumes:
|
||||
- broker:/var/lib/rabbitmq
|
||||
results:
|
||||
container_name: mayan-edms-results
|
||||
image: healthcheck/redis
|
||||
volumes:
|
||||
- results:/data
|
||||
db:
|
||||
container_name: mayan-edms-db
|
||||
image: healthcheck/postgres
|
||||
environment:
|
||||
POSTGRES_DB: mayan
|
||||
POSTGRES_PASSWORD: mayan-password
|
||||
POSTGRES_USER: mayan
|
||||
volumes:
|
||||
- db:/var/lib/postgresql/data
|
||||
mayan-edms:
|
||||
container_name: mayan-edms-app
|
||||
image: mayanedms/mayanedms:latest
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: ./docker/Dockerfile
|
||||
depends_on:
|
||||
broker:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
results:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MAYAN_BROKER_URL: amqp://mayan:mayan@broker:5672/mayan
|
||||
MAYAN_CELERY_RESULT_BACKEND: redis://results:6379/0
|
||||
MAYAN_DATABASE_ENGINE: django.db.backends.postgresql
|
||||
MAYAN_DATABASE_HOST: db
|
||||
MAYAN_DATABASE_NAME: mayan
|
||||
MAYAN_DATABASE_PASSWORD: mayan-password
|
||||
MAYAN_DATABASE_USER: mayan
|
||||
- postgresql
|
||||
- redis
|
||||
# Enable to use RabbitMQ
|
||||
#- rabbitmq
|
||||
environment: &mayan_env
|
||||
# Enable to use RabbitMQ
|
||||
# MAYAN_CELERY_BROKER_URL: amqp://mayan:mayanrabbitpass@broker:5672/mayan
|
||||
# Disable Redis Broker to use RabbitMQ as Broker
|
||||
MAYAN_CELERY_BROKER_URL: redis://redis:6379/1
|
||||
MAYAN_CELERY_RESULT_BACKEND: redis://redis:6379/0
|
||||
MAYAN_DATABASES: "{'default':{'ENGINE':'django.db.backends.postgresql','NAME':'mayan','PASSWORD':'mayandbpass','USER':'mayan','HOST':'postgresql'}}"
|
||||
image: mayanedms/mayanedms:3.2.6
|
||||
networks:
|
||||
- mayan-bridge
|
||||
ports:
|
||||
- "80:8000"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- app:/var/lib/mayan
|
||||
- /docker-volumes/mayan-edms/media:/var/lib/mayan
|
||||
|
||||
postgresql:
|
||||
environment:
|
||||
POSTGRES_DB: mayan
|
||||
POSTGRES_PASSWORD: mayandbpass
|
||||
POSTGRES_USER: mayan
|
||||
image: postgres:9.6
|
||||
networks:
|
||||
- mayan-bridge
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /docker-volumes/mayan-edms/postgres:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
command:
|
||||
- redis-server
|
||||
- --databases
|
||||
- "2"
|
||||
- --maxmemory-policy
|
||||
- allkeys-lru
|
||||
- --save
|
||||
- ""
|
||||
image: redis:5.0
|
||||
networks:
|
||||
- mayan-bridge
|
||||
restart: unless-stopped
|
||||
|
||||
# Optional services
|
||||
|
||||
# celery_flower:
|
||||
# command:
|
||||
# - run_celery
|
||||
# - flower
|
||||
# depends_on:
|
||||
# - postgresql
|
||||
# - redis
|
||||
# # Enable to use RabbitMQ
|
||||
# # - rabbitmq
|
||||
# environment:
|
||||
# <<: *mayan_env
|
||||
# image: mayanedms/mayanedms:3.2.6
|
||||
# networks:
|
||||
# - mayan-bridge
|
||||
# ports:
|
||||
# - "5555:5555"
|
||||
# restart: unless-stopped
|
||||
|
||||
# Enable to use RabbitMQ
|
||||
# rabbitmq:
|
||||
# container_name: mayan-edms-rabbitmq
|
||||
# image: healthcheck/rabbitmq
|
||||
# environment:
|
||||
# RABBITMQ_DEFAULT_USER: mayan
|
||||
# RABBITMQ_DEFAULT_PASS: mayanrabbitpass
|
||||
# RABBITMQ_DEFAULT_VHOST: mayan
|
||||
# networks:
|
||||
# - mayan-bridge
|
||||
# restart: unless-stopped
|
||||
# volumes:
|
||||
# - /docker-volumes/mayan-edms/rabbitmq:/var/lib/rabbitmq
|
||||
|
||||
# Enable to run stand alone workers
|
||||
# worker_fast:
|
||||
# command:
|
||||
# - run_worker
|
||||
# - fast
|
||||
# depends_on:
|
||||
# - postgresql
|
||||
# - redis
|
||||
# # Enable to use RabbitMQ
|
||||
# # - rabbitmq
|
||||
# environment:
|
||||
# <<: *mayan_env
|
||||
# image: mayanedms/mayanedms:3.2.6
|
||||
# networks:
|
||||
# - mayan-bridge
|
||||
# restart: unless-stopped
|
||||
# volumes:
|
||||
# - /docker-volumes/mayan-edms/media:/var/lib/mayan
|
||||
|
||||
# Enable to run stand frontend gunicorn
|
||||
# frontend:
|
||||
# command:
|
||||
# - run_frontend
|
||||
# depends_on:
|
||||
# - postgresql
|
||||
# - redis
|
||||
# # Enable to use RabbitMQ
|
||||
# # - rabbitmq
|
||||
# environment:
|
||||
# <<: *mayan_env
|
||||
# image: mayanedms/mayanedms:3.2.6
|
||||
# networks:
|
||||
# - mayan-bridge
|
||||
# ports:
|
||||
# - "81:8000"
|
||||
# restart: unless-stopped
|
||||
# volumes:
|
||||
# - /docker-volumes/mayan-edms/media:/var/lib/mayan
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
# Use bash and not sh to support argument slicing "${@:2}"
|
||||
# sh defaults to dash instead of bash.
|
||||
|
||||
set -e
|
||||
echo "mayan: starting entrypoint.sh"
|
||||
@@ -11,17 +14,13 @@ DEFAULT_USER_GID=1000
|
||||
MAYAN_USER_UID=${MAYAN_USER_UID:-${DEFAULT_USER_UID}}
|
||||
MAYAN_USER_GID=${MAYAN_USER_GID:-${DEFAULT_USER_GID}}
|
||||
|
||||
export MAYAN_DEFAULT_BROKER_URL=redis://127.0.0.1:6379/0
|
||||
export MAYAN_DEFAULT_CELERY_RESULT_BACKEND=redis://127.0.0.1:6379/0
|
||||
|
||||
export MAYAN_ALLOWED_HOSTS='["*"]'
|
||||
export MAYAN_BIN=/opt/mayan-edms/bin/mayan-edms.py
|
||||
export MAYAN_BROKER_URL=${MAYAN_BROKER_URL:-${MAYAN_DEFAULT_BROKER_URL}}
|
||||
export MAYAN_CELERY_RESULT_BACKEND=${MAYAN_CELERY_RESULT_BACKEND:-${MAYAN_DEFAULT_CELERY_RESULT_BACKEND}}
|
||||
export MAYAN_INSTALL_DIR=/opt/mayan-edms
|
||||
export MAYAN_PYTHON_BIN_DIR=/opt/mayan-edms/bin/
|
||||
export MAYAN_MEDIA_ROOT=/var/lib/mayan
|
||||
export MAYAN_SETTINGS_MODULE=${MAYAN_SETTINGS_MODULE:-mayan.settings.production}
|
||||
export DJANGO_SETTINGS_MODULE=${MAYAN_SETTINGS_MODULE}
|
||||
|
||||
export MAYAN_GUNICORN_BIN=${MAYAN_PYTHON_BIN_DIR}gunicorn
|
||||
export MAYAN_GUNICORN_WORKERS=${MAYAN_GUNICORN_WORKERS:-2}
|
||||
@@ -29,8 +28,8 @@ export MAYAN_GUNICORN_TIMEOUT=${MAYAN_GUNICORN_TIMEOUT:-120}
|
||||
export MAYAN_PIP_BIN=${MAYAN_PYTHON_BIN_DIR}pip
|
||||
export MAYAN_STATIC_ROOT=${MAYAN_INSTALL_DIR}/static
|
||||
|
||||
MAYAN_WORKER_FAST_CONCURRENCY=${MAYAN_WORKER_FAST_CONCURRENCY:-1}
|
||||
MAYAN_WORKER_MEDIUM_CONCURRENCY=${MAYAN_WORKER_MEDIUM_CONCURRENCY:-1}
|
||||
MAYAN_WORKER_FAST_CONCURRENCY=${MAYAN_WORKER_FAST_CONCURRENCY:-0}
|
||||
MAYAN_WORKER_MEDIUM_CONCURRENCY=${MAYAN_WORKER_MEDIUM_CONCURRENCY:-0}
|
||||
MAYAN_WORKER_SLOW_CONCURRENCY=${MAYAN_WORKER_SLOW_CONCURRENCY:-1}
|
||||
|
||||
update_uid_gid() {
|
||||
@@ -67,11 +66,9 @@ else
|
||||
fi
|
||||
export MAYAN_WORKER_SLOW_CONCURRENCY
|
||||
|
||||
export CELERY_ALWAYS_EAGER=False
|
||||
# Allow importing of user setting modules
|
||||
export PYTHONPATH=$PYTHONPATH:$MAYAN_MEDIA_ROOT
|
||||
|
||||
chown mayan:mayan /var/lib/mayan -R
|
||||
|
||||
apt_get_install() {
|
||||
apt-get -q update
|
||||
apt-get install -y --force-yes --no-install-recommends --auto-remove "$@"
|
||||
@@ -79,9 +76,9 @@ apt_get_install() {
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
}
|
||||
|
||||
initialize() {
|
||||
echo "mayan: initialize()"
|
||||
su mayan -c "${MAYAN_BIN} initialsetup --force --no-javascript"
|
||||
initialsetup() {
|
||||
echo "mayan: initialsetup()"
|
||||
su mayan -c "${MAYAN_BIN} initialsetup --force --no-dependencies"
|
||||
}
|
||||
|
||||
os_package_installs() {
|
||||
@@ -98,43 +95,71 @@ pip_installs() {
|
||||
fi
|
||||
}
|
||||
|
||||
start() {
|
||||
run_all() {
|
||||
echo "mayan: start()"
|
||||
rm -rf /var/run/supervisor.sock
|
||||
exec /usr/bin/supervisord -nc /etc/supervisor/supervisord.conf
|
||||
}
|
||||
|
||||
upgrade() {
|
||||
echo "mayan: upgrade()"
|
||||
su mayan -c "${MAYAN_BIN} performupgrade --no-javascript"
|
||||
performupgrade() {
|
||||
echo "mayan: performupgrade()"
|
||||
su mayan -c "${MAYAN_BIN} performupgrade --no-dependencies"
|
||||
}
|
||||
|
||||
make_ready() {
|
||||
# Check if this is a new install, otherwise try to upgrade the existing
|
||||
# installation on subsequent starts
|
||||
if [ ! -f $INSTALL_FLAG ]; then
|
||||
initialsetup
|
||||
else
|
||||
performupgrade
|
||||
fi
|
||||
}
|
||||
|
||||
set_uid_guid() {
|
||||
echo "mayan: changing uid/guid"
|
||||
usermod mayan -u ${MAYAN_USER_UID:-${DEFAULT_USER_UID}}
|
||||
groupmod mayan -g ${MAYAN_USER_GID:-${DEFAULT_USER_GID}}
|
||||
}
|
||||
|
||||
os_package_installs || true
|
||||
pip_installs || true
|
||||
chown mayan:mayan /var/lib/mayan -R
|
||||
|
||||
case "$1" in
|
||||
|
||||
mayan) # Check if this is a new install, otherwise try to upgrade the existing
|
||||
# installation on subsequent starts
|
||||
if [ ! -f $INSTALL_FLAG ]; then
|
||||
initialize
|
||||
else
|
||||
upgrade
|
||||
fi
|
||||
start
|
||||
run_initialsetup)
|
||||
initialsetup
|
||||
;;
|
||||
|
||||
run-tests) # Check if this is a new install, otherwise try to upgrade the existing
|
||||
# installation on subsequent starts
|
||||
if [ ! -f $INSTALL_FLAG ]; then
|
||||
initialize
|
||||
else
|
||||
upgrade
|
||||
fi
|
||||
run-tests.sh
|
||||
run_performupgrade)
|
||||
performupgrade
|
||||
;;
|
||||
|
||||
*) su mayan -c "$@";
|
||||
run_all)
|
||||
make_ready
|
||||
run_all
|
||||
;;
|
||||
|
||||
run_celery)
|
||||
run_celery.sh "${@:2}"
|
||||
;;
|
||||
|
||||
run_frontend)
|
||||
run_frontend.sh
|
||||
;;
|
||||
|
||||
run_tests)
|
||||
make_ready
|
||||
run_tests.sh
|
||||
;;
|
||||
|
||||
run_worker)
|
||||
run_worker.sh "${@:2}"
|
||||
;;
|
||||
|
||||
*)
|
||||
su mayan -c "$@"
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
5
docker/rootfs/usr/local/bin/run_celery.sh
Executable file
5
docker/rootfs/usr/local/bin/run_celery.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Use -A and not --app. Both are the same but behave differently
|
||||
# -A can be located before the command while --app cannot.
|
||||
su mayan -c "${MAYAN_PYTHON_BIN_DIR}celery -A mayan $@"
|
||||
7
docker/rootfs/usr/local/bin/run_frontend.sh
Executable file
7
docker/rootfs/usr/local/bin/run_frontend.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
MAYAN_GUNICORN_MAX_REQUESTS=${MAYAN_GUNICORN_MAX_REQUESTS:-500}
|
||||
MAYAN_GUNICORN_MAX_REQUESTS_JITTERS=${MAYAN_GUNICORN_MAX_REQUESTS_JITTERS:-50}
|
||||
MAYAN_GUNICORN_WORKER_CLASS=${MAYAN_GUNICORN_WORKER_CLASS:-sync}
|
||||
|
||||
su mayan -c "${MAYAN_PYTHON_BIN_DIR}gunicorn -w ${MAYAN_GUNICORN_WORKERS} mayan.wsgi --max-requests ${MAYAN_GUNICORN_MAX_REQUESTS} --max-requests-jitter ${MAYAN_GUNICORN_MAX_REQUESTS_JITTERS} --worker-class ${MAYAN_GUNICORN_WORKER_CLASS} --bind 0.0.0.0:8000 --timeout ${MAYAN_GUNICORN_TIMEOUT}"
|
||||
8
docker/rootfs/usr/local/bin/run_worker.sh
Executable file
8
docker/rootfs/usr/local/bin/run_worker.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
QUEUE_LIST=`MAYAN_WORKER_NAME=$1 su mayan -c "${MAYAN_PYTHON_BIN_DIR}mayan-edms.py platformtemplate worker_queues"`
|
||||
|
||||
# Use -A and not --app. Both are the same but behave differently
|
||||
# -A can be located before the command while --app cannot.
|
||||
# Pass ${@:2} to allow overriding the defaults arguments
|
||||
su mayan -c "${MAYAN_PYTHON_BIN_DIR}celery -A mayan worker -Ofair -l ERROR -Q $QUEUE_LIST ${@:2}"
|
||||
@@ -1 +1 @@
|
||||
3.2.7
|
||||
3.3beta1
|
||||
|
||||
@@ -9,24 +9,32 @@ volumes:
|
||||
services:
|
||||
|
||||
db:
|
||||
image: postgres
|
||||
environment:
|
||||
POSTGRES_DB: mayan
|
||||
POSTGRES_PASSWORD: mayan-password
|
||||
POSTGRES_PASSWORD: mayandbpass
|
||||
POSTGRES_USER: mayan
|
||||
image: postgres
|
||||
volumes:
|
||||
- db:/var/lib/postgresql/data
|
||||
|
||||
app:
|
||||
environment:
|
||||
MAYAN_CELERY_BROKER_URL: redis://redis:6379/1
|
||||
MAYAN_CELERY_RESULT_BACKEND: redis://redis:6379/0
|
||||
MAYAN_DATABASES: "{'default':{'ENGINE':'django.db.backends.postgresql','NAME':'mayan','PASSWORD':'mayandbpass','USER':'mayan','HOST':'db'}}"
|
||||
image: mayanedms/mayanedms:latest
|
||||
ports:
|
||||
- 80:8000
|
||||
environment:
|
||||
MAYAN_DATABASE_ENGINE: django.db.backends.postgresql
|
||||
MAYAN_DATABASE_HOST: db
|
||||
MAYAN_DATABASE_NAME: mayan
|
||||
MAYAN_DATABASE_PASSWORD: mayan-password
|
||||
MAYAN_DATABASE_USER: mayan
|
||||
MAYAN_DATABASE_CONN_MAX_AGE: 0
|
||||
volumes:
|
||||
- app:/var/lib/mayan
|
||||
|
||||
redis:
|
||||
command:
|
||||
- redis-server
|
||||
- --databases
|
||||
- "2"
|
||||
- --maxmemory-policy
|
||||
- allkeys-lru
|
||||
- --save
|
||||
- ""
|
||||
image: redis:5.0
|
||||
|
||||
@@ -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 \
|
||||
MAYAN_DATABASE_PASSWORD=mayanuserpass MAYAN_DATABASE_USER=mayan \
|
||||
MAYAN_DATABASE_HOST=127.0.0.1 MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
||||
sudo -u mayan MAYAN_DATABASES="{'default':{'ENGINE':'django.db.backends.postgresql','NAME':'mayan','PASSWORD':'mayanuserpass','USER':'mayan','HOST':'127.0.0.1'}}" \
|
||||
MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
||||
/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 \
|
||||
MAYAN_DATABASE_PASSWORD=mayanuserpass MAYAN_DATABASE_USER=mayan \
|
||||
MAYAN_DATABASE_HOST=127.0.0.1 MAYAN_MEDIA_ROOT=/opt/mayan-edms/media \
|
||||
sudo mayan MAYAN_DATABASES="{'default':{'ENGINE':'django.db.backends.postgresql','NAME':'mayan','PASSWORD':'mayanuserpass','USER':'mayan','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
|
||||
|
||||
|
||||
@@ -222,11 +220,11 @@ of a restart or power failure. The Gunicorn workers are increased to 3.
|
||||
---------------------------------------------------------------------
|
||||
Replace (paying attention to the comma at the end)::
|
||||
|
||||
MAYAN_BROKER_URL="redis://127.0.0.1:6379/0",
|
||||
MAYAN_CELERY_BROKER_URL="redis://127.0.0.1:6379/0",
|
||||
|
||||
with::
|
||||
|
||||
MAYAN_BROKER_URL="amqp://mayan:mayanrabbitmqpassword@localhost:5672/mayan",
|
||||
MAYAN_CELERY_BROKER_URL="amqp://mayan:mayanrabbitmqpassword@localhost:5672/mayan",
|
||||
|
||||
increase the number of Gunicorn workers to 3 in the line (``-w 2`` section)::
|
||||
|
||||
|
||||
@@ -533,7 +533,7 @@ Release using GitLab CI
|
||||
::
|
||||
|
||||
git checkout releases/all
|
||||
git merge versions/next
|
||||
git merge <corresponding branch>
|
||||
|
||||
#. Push code to trigger builds:
|
||||
::
|
||||
|
||||
@@ -49,12 +49,7 @@ Finally create and run a Mayan EDMS container::
|
||||
--name mayan-edms \
|
||||
--restart=always \
|
||||
-p 80:8000 \
|
||||
-e MAYAN_DATABASE_ENGINE=django.db.backends.postgresql \
|
||||
-e MAYAN_DATABASE_HOST=172.17.0.1 \
|
||||
-e MAYAN_DATABASE_NAME=mayan \
|
||||
-e MAYAN_DATABASE_PASSWORD=mayanuserpass \
|
||||
-e MAYAN_DATABASE_USER=mayan \
|
||||
-e MAYAN_DATABASE_CONN_MAX_AGE=0 \
|
||||
-e MAYAN_DATABASES="{'default':{'ENGINE':'django.db.backends.postgresql','NAME':'mayan','PASSWORD':'mayanuserpass','USER':'mayan','HOST':'172.17.0.1'}}" \
|
||||
-v /docker-volumes/mayan-edms/media:/var/lib/mayan \
|
||||
mayanedms/mayanedms:<version>
|
||||
|
||||
@@ -108,12 +103,7 @@ instead of the IP address of the Docker host (``172.17.0.1``)::
|
||||
--network=mayan \
|
||||
--restart=always \
|
||||
-p 80:8000 \
|
||||
-e MAYAN_DATABASE_ENGINE=django.db.backends.postgresql \
|
||||
-e MAYAN_DATABASE_HOST=mayan-edms-postgres \
|
||||
-e MAYAN_DATABASE_NAME=mayan \
|
||||
-e MAYAN_DATABASE_PASSWORD=mayanuserpass \
|
||||
-e MAYAN_DATABASE_USER=mayan \
|
||||
-e MAYAN_DATABASE_CONN_MAX_AGE=0 \
|
||||
-e MAYAN_DATABASES="{'default':{'ENGINE':'django.db.backends.postgresql','NAME':'mayan','PASSWORD':'mayanuserpass','USER':'mayan','HOST':'mayan-edms-postgres'}}" \
|
||||
-v /docker-volumes/mayan-edms/media:/var/lib/mayan \
|
||||
mayanedms/mayanedms:<version>
|
||||
|
||||
@@ -137,101 +127,14 @@ To start the container again::
|
||||
Environment Variables
|
||||
---------------------
|
||||
|
||||
The Mayan EDMS image can be configure via environment variables.
|
||||
|
||||
``MAYAN_DATABASE_ENGINE``
|
||||
|
||||
Defaults to ``None``. This environment variable configures the database
|
||||
backend to use. If left unset, SQLite will be used. The database backends
|
||||
supported by this Docker image are:
|
||||
|
||||
- ``'django.db.backends.postgresql'``
|
||||
- ``'django.db.backends.mysql'``
|
||||
- ``'django.db.backends.sqlite3'``
|
||||
|
||||
When using the SQLite backend, the database file will be saved in the Docker
|
||||
volume. The SQLite database as used by Mayan EDMS is meant only for development
|
||||
or testing, never use it in production.
|
||||
|
||||
``MAYAN_DATABASE_NAME``
|
||||
|
||||
Defaults to 'mayan'. This optional environment variable can be used to define
|
||||
the database name that Mayan EDMS will connect to. For more information read
|
||||
the pertinent Django documentation page:
|
||||
:django-docs:`Connecting to the database <ref/databases/#connecting-to-the-database>`
|
||||
|
||||
``MAYAN_DATABASE_USER``
|
||||
|
||||
Defaults to 'mayan'. This optional environment variable is used to set the
|
||||
username that will be used to connect to the database. For more information
|
||||
read the pertinent Django documentation page:
|
||||
:django-docs:`Settings, USER <ref/settings/#user>`
|
||||
|
||||
``MAYAN_DATABASE_PASSWORD``
|
||||
|
||||
Defaults to ''. This optional environment variable is used to set the
|
||||
password that will be used to connect to the database. For more information
|
||||
read the pertinent Django documentation page:
|
||||
:django-docs:`Settings, PASSWORD <ref/settings/#password>`
|
||||
|
||||
``MAYAN_DATABASE_HOST``
|
||||
|
||||
Defaults to `None`. This optional environment variable is used to set the
|
||||
hostname that will be used to connect to the database. This can be the
|
||||
hostname of another container or an IP address. For more information read
|
||||
the pertinent Django documentation page:
|
||||
:django-docs:`Settings, HOST <ref/settings/#host>`
|
||||
|
||||
``MAYAN_DATABASE_PORT``
|
||||
|
||||
Defaults to `None`. This optional environment variable is used to set the
|
||||
port number to use when connecting to the database. An empty string means
|
||||
the default port. Not used with SQLite. For more information read the
|
||||
pertinent Django documentation page:
|
||||
:django-docs:`Settings, PORT <ref/settings/#port>`
|
||||
|
||||
``MAYAN_BROKER_URL``
|
||||
|
||||
This optional environment variable determines the broker that Celery will use
|
||||
to relay task messages between the frontend code and the background workers.
|
||||
For more information read the pertinent Celery Kombu documentation page: `Broker URL`_
|
||||
|
||||
.. _Broker URL: http://kombu.readthedocs.io/en/latest/userguide/connections.html#connection-urls
|
||||
|
||||
This Docker image supports using Redis and RabbitMQ as brokers.
|
||||
|
||||
Caveat: If the `MAYAN_BROKER_URL` and `MAYAN_CELERY_RESULT_BACKEND` environment
|
||||
variables are specified, the built-in Redis server inside the container will
|
||||
be disabled.
|
||||
|
||||
``MAYAN_CELERY_RESULT_BACKEND``
|
||||
|
||||
This optional environment variable determines the results backend that Celery
|
||||
will use to relay result messages from the background workers to the frontend
|
||||
code. For more information read the pertinent Celery Kombu documentation page:
|
||||
`Task result backend settings`_
|
||||
|
||||
.. _Task result backend settings: http://docs.celeryproject.org/en/3.1/configuration.html#celery-result-backend
|
||||
|
||||
This Docker image supports using Redis and RabbitMQ as result backends.
|
||||
|
||||
Caveat: If the `MAYAN_BROKER_URL` and `MAYAN_CELERY_RESULT_BACKEND` environment
|
||||
variables are specified, the built-in Redis server inside the container will
|
||||
be disabled.
|
||||
The common set of settings can also be modified via environment variables when
|
||||
using the Docker image. In addition to the common set of settings, some Docker
|
||||
image specific environment variables are available.
|
||||
|
||||
``MAYAN_SETTINGS_MODULE``
|
||||
|
||||
Optional. Allows loading an alternate settings file.
|
||||
|
||||
``MAYAN_DATABASE_CONN_MAX_AGE``
|
||||
|
||||
Amount in seconds to keep a database connection alive. Allow reuse of database
|
||||
connections. For more information read the pertinent Django documentation
|
||||
page: :django-docs:`Settings, CONN_MAX_AGE <ref/settings/#conn-max-age>`
|
||||
According to new information Gunicorn's microthreads don't share connections
|
||||
and will exhaust the available Postgres connections available if a number
|
||||
other than 0 is used. Reference: https://serverfault.com/questions/635100/django-conn-max-age-persists-connections-but-doesnt-reuse-them-with-postgresq
|
||||
and https://github.com/benoitc/gunicorn/issues/996
|
||||
|
||||
``MAYAN_GUNICORN_TIMEOUT``
|
||||
|
||||
@@ -281,6 +184,15 @@ Optional. Changes the GID of the ``mayan`` user internal to the Docker
|
||||
container. Defaults to 1000.
|
||||
|
||||
|
||||
Included drivers
|
||||
----------------
|
||||
|
||||
The Docker image supports using Redis and RabbitMQ as result backends. For
|
||||
databases, the image includes support for PostgreSQL and MySQL/MariaDB.
|
||||
Support for additional brokers or databases may be added using the
|
||||
``MAYAN_APT_INSTALL`` environment variable.
|
||||
|
||||
|
||||
.. _docker-accessing-outside-data:
|
||||
|
||||
Accessing outside data
|
||||
@@ -448,6 +360,7 @@ These are:
|
||||
|
||||
Nightly images
|
||||
==============
|
||||
|
||||
The continuous integration pipeline used for testing development builds also
|
||||
produces a resulting Docker image. These are build automatically and their
|
||||
stability is not guaranteed. They should never be used in production.
|
||||
|
||||
@@ -94,11 +94,11 @@ For the Docker image, launch a separate RabbitMQ container
|
||||
|
||||
docker run -d --name mayan-edms-rabbitmq -e RABBITMQ_DEFAULT_USER=mayan -e RABBITMQ_DEFAULT_PASS=mayanrabbitmqpassword -e RABBITMQ_DEFAULT_VHOST=mayan rabbitmq:3
|
||||
|
||||
Pass the MAYAN_BROKER_URL environment variable (https://kombu.readthedocs.io/en/latest/userguide/connections.html#connection-urls)
|
||||
Pass the MAYAN_CELERY_BROKER_URL environment variable (https://kombu.readthedocs.io/en/latest/userguide/connections.html#connection-urls)
|
||||
to the Mayan EDMS container so that it uses the RabbitMQ container the
|
||||
message broker::
|
||||
|
||||
-e MAYAN_BROKER_URL="amqp://mayan:mayanrabbitmqpassword@localhost:5672/mayan",
|
||||
-e MAYAN_CELERY_BROKER_URL="amqp://mayan:mayanrabbitmqpassword@localhost:5672/mayan",
|
||||
|
||||
When tasks finish, they leave behind a return status or the result of a
|
||||
calculation, these are stored for a while so that whoever requested the
|
||||
|
||||
@@ -19,7 +19,6 @@ Changes
|
||||
GitLab issue #625. Thanks to Jesaja Everling (@jeverling)
|
||||
for the report and the research.
|
||||
|
||||
|
||||
Removals
|
||||
--------
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ Changes
|
||||
incorrectly state it is named flag_list. Closes GitLab issue
|
||||
#606. Thanks to Samuel Aebi (@samuelaebi) for the report and
|
||||
debug information.
|
||||
debug information.
|
||||
- Support configurable GUnicorn timeouts. Defaults to
|
||||
current value of 120 seconds.
|
||||
- Fix help text of the platformtemplate command.
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
Version 3.2.8
|
||||
=============
|
||||
|
||||
Released: XX, 2019
|
||||
Released: October 1, 2019
|
||||
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
|
||||
API
|
||||
^^^
|
||||
|
||||
Fix an error when accessing some API entry points without
|
||||
being authenticated. Accessing API endpoints without being authenticated
|
||||
will now always return empty results.
|
||||
|
||||
|
||||
Cabinets
|
||||
^^^^^^^^
|
||||
|
||||
@@ -14,25 +23,43 @@ Tweaked the jstree component's appearance to cope with long labels.
|
||||
Added a scrollbar, reduced the font size, switched to a sans serif font,
|
||||
and reduced padding. Thanks for forum user @briboe for the report.
|
||||
|
||||
Workflow actions to add and remove documents from cabinets was added.
|
||||
|
||||
Other changes
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
- Fix error when accessing some API entry points without
|
||||
being authenticated.
|
||||
- Add cabinet add and remove workflow actions.
|
||||
- Update Django to version 1.11.24.
|
||||
- Update jQuery to version 3.4.1
|
||||
- Add support for deleting the OCR content of a document
|
||||
or selection of documents.
|
||||
- Add OCR content deleted event.
|
||||
- Add missing recursive option to Docker entrypoint
|
||||
chown. GitLab issue #668. Thanks to John Wice (@brilthor)
|
||||
for the report.
|
||||
- Add support for deleting the parsed content of a document
|
||||
of selection of documents.
|
||||
- Add parsed content deleted event.
|
||||
- Allow scaling of UI on mobile devices.
|
||||
Dependencies
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The Django version used was updated to version 1.11.24. The jQuery version
|
||||
used was updated to version 3.4.1. Both as fully backwards compatible with
|
||||
their previous versions.
|
||||
|
||||
|
||||
OCR
|
||||
^^^
|
||||
|
||||
Support was added to delete the content of document's OCR or parsed content.
|
||||
Events for both situations was added allowing content deletion to be used
|
||||
as workflow transition triggers.
|
||||
|
||||
|
||||
Docker
|
||||
^^^^^^
|
||||
|
||||
A missing recursive option was added to the Docker entrypoint
|
||||
command "chown" to change the ownership of files when specifying a custom
|
||||
UID or GID. Closes GitLab issue #668. Thanks to John Wice (@brilthor)
|
||||
for the report.
|
||||
|
||||
Two fonts were added to the Docker image to support rendering Chinese office
|
||||
documents. Closes GitLab issue #666. Thanks to javawcy (@javawcy) and forum
|
||||
user @leoliu for the report and help closing this issue.
|
||||
|
||||
|
||||
Usability
|
||||
^^^^^^^^^
|
||||
|
||||
Descriptions for screenreaders was added via image alt tag. The user interface
|
||||
will also now allow scaling.
|
||||
|
||||
|
||||
Removals
|
||||
@@ -126,7 +153,9 @@ Backward incompatible changes
|
||||
Bugs fixed or issues closed
|
||||
---------------------------
|
||||
|
||||
- :gitlab-issue:`666` Chinese document such as .doc can't display well.
|
||||
- :gitlab-issue:`668` Permission denied errors with custom uid persist (650 needs re-open)
|
||||
- :forum-topic:`1120` Cabinet Presentation
|
||||
- :forum-topic:`2202` Cannot display Chinese character and cannot identify Excel files
|
||||
|
||||
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/
|
||||
|
||||
213
docs/releases/3.3.rst
Normal file
213
docs/releases/3.3.rst
Normal file
@@ -0,0 +1,213 @@
|
||||
Version 3.3
|
||||
===========
|
||||
|
||||
Released: XX XX, 2019
|
||||
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Add support for icon shadows.
|
||||
- Add icons and no-result template to the object error log view and
|
||||
links.
|
||||
- Use Select2 widget for the document type selection form.
|
||||
- Backport the vertical main menu update. This update splits the previous
|
||||
main menu into a new menu in the same location as the previous one
|
||||
now called the top bar, and a new vertical main menu on the left side.
|
||||
The vertical menu remain open even when clicking on items and upon
|
||||
a browser refresh will also restore its state to match the selected
|
||||
view.
|
||||
- Backport workflow preview refactor. GitLab issue #532.
|
||||
- Add support for source column inheritance.
|
||||
- Add support for source column exclusion.
|
||||
- Backport workflow context 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.
|
||||
- Backport file cache manager app.
|
||||
- Convert document image cache to use file cache manager app.
|
||||
Add setting DOCUMENTS_CACHE_MAXIMUM_SIZE defaults to 500 MB.
|
||||
- Update Celery to version 4.3.0. Settings changed:
|
||||
MAYAN_BROKER_URL to MAYAN_CELERY_BROKER_URL,
|
||||
MAYAN_CELERY_ALWAYS_EAGER to MAYAN_CELERY_TASK_ALWAYS_EAGER.
|
||||
- Replace djcelery and replace it with django-celery-beat.
|
||||
- Update Celery to version 4.3.0 with 55e9b2263cbdb9b449361412fd18d8ee0a442dd3
|
||||
from versions/next, code from GitLab issue #594 and GitLab merge request !55.
|
||||
Thanks to Jakob Haufe (@sur5r) and Jesaja Everling (@jeverling)
|
||||
for much of the research and code updates.
|
||||
- Support wildcard MIME type associations for the file metadata drivers.
|
||||
- Rename MAYAN_GUID to MAYAN_GID
|
||||
- Update Gunicorn to use sync workers.
|
||||
- Include devpi-server as a development dependency.
|
||||
- Update default Docker stack file.
|
||||
- Remove Redis from the Docker image.
|
||||
- Add Celery flower to the Docker image.
|
||||
- Allow PIP proxying to the Docker image during build.
|
||||
- Default Celery worker concurrency to 0 (auto).
|
||||
- Set DJANGO_SETTINGS_MODULE environment variable to make it
|
||||
available to sub processes.
|
||||
- Add entrypoint commands to run single workers, single gunicorn
|
||||
or single celery commands like "flower".
|
||||
- Add platform template to return queues for a worker.
|
||||
- Remove task inspection from task manager app.
|
||||
- Move pagination navigation inside the toolbar.
|
||||
- Remove document image clear link and view.
|
||||
This is now handled by the file caching app.
|
||||
- Add web links app.
|
||||
- Add support to display column help text
|
||||
as a tooltip.
|
||||
- Update numeric dashboard widget to display
|
||||
thousand commas.
|
||||
- Add support for disabling document pages.
|
||||
- Add support for converter layers.
|
||||
- Add redactions app.
|
||||
- Unify all line endings to be Linux style.
|
||||
- Add support for changing the system messages position.
|
||||
GitLab issue #640. Thanks to Matthias Urhahn (@d4rken).
|
||||
|
||||
Removals
|
||||
--------
|
||||
|
||||
- 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
|
||||
---------------------------------
|
||||
|
||||
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::
|
||||
|
||||
/opt/mayan-edms/bin/pip install mayan-edms==3.3
|
||||
|
||||
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_DATABASES="{'default':{'ENGINE':'django.db.backends.postgresql','NAME':'mayan','PASSWORD':'mayanuserpass','USER':'mayan','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_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
|
||||
-----------------------------
|
||||
|
||||
- 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
|
||||
---------------------------
|
||||
|
||||
- :gitlab-issue:`526` RuntimeWarning: Never call result.get() within a task!
|
||||
- :gitlab-issue:`532` Workflow preview isn't updated right after transitions are modified
|
||||
- :gitlab-issue:`540` hint-outdated/update documentation
|
||||
- :gitlab-issue:`594` 3.2b1: Unable to install/run under Python 3.5/3.6/3.7
|
||||
- :gitlab-issue:`634` Failing docker entrypoint when using secret config
|
||||
- :gitlab-issue:`635` Build a docker image for Python3
|
||||
- :gitlab-issue:`640` UX: "Toast" Popup position prevents access to actions
|
||||
- :gitlab-issue:`644` Update sane-utils package in docker image.
|
||||
|
||||
|
||||
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/
|
||||
@@ -20,6 +20,7 @@ versions of the documentation contain the release notes for any later releases.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
3.3
|
||||
3.2.8
|
||||
3.2.7
|
||||
3.2.6
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__title__ = 'Mayan EDMS'
|
||||
__version__ = '3.2.7'
|
||||
__build__ = 0x030207
|
||||
__build_string__ = 'v3.2.7_Wed Aug 28 17:31:08 2019 -0400'
|
||||
__version__ = '3.3beta1'
|
||||
__build__ = 0x030208
|
||||
__build_string__ = 'v3.2.8-255-g69086d87dd_Tue Oct 8 09:43:10 2019 -0400'
|
||||
__django_version__ = '1.11'
|
||||
__author__ = 'Roberto Rosario'
|
||||
__author_email__ = 'roberto.rosario@mayan-edms.com'
|
||||
|
||||
@@ -12,6 +12,7 @@ logger = logging.getLogger(__name__)
|
||||
class ModelPermission(object):
|
||||
_functions = {}
|
||||
_inheritances = {}
|
||||
_manager_names = {}
|
||||
_registry = {}
|
||||
|
||||
@classmethod
|
||||
@@ -20,22 +21,6 @@ class ModelPermission(object):
|
||||
# TODO: Find method to revert the add_to_class('acls'...)
|
||||
# delattr doesn't work.
|
||||
|
||||
@classmethod
|
||||
def register(cls, model, permissions):
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
|
||||
cls._registry.setdefault(model, [])
|
||||
for permission in permissions:
|
||||
cls._registry[model].append(permission)
|
||||
|
||||
AccessControlList = apps.get_model(
|
||||
app_label='acls', model_name='AccessControlList'
|
||||
)
|
||||
|
||||
model.add_to_class(
|
||||
name='acls', value=GenericRelation(AccessControlList)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_classes(cls, as_content_type=False):
|
||||
ContentType = apps.get_model(
|
||||
@@ -97,6 +82,40 @@ class ModelPermission(object):
|
||||
def get_inheritance(cls, model):
|
||||
return cls._inheritances[model]
|
||||
|
||||
@classmethod
|
||||
def get_manager(cls, model):
|
||||
try:
|
||||
manager_name = cls.get_manager_name(model=model)
|
||||
except KeyError:
|
||||
manager_name = None
|
||||
|
||||
if manager_name:
|
||||
manager = getattr(model, manager_name)
|
||||
else:
|
||||
manager = model._meta.default_manager
|
||||
|
||||
return manager
|
||||
|
||||
@classmethod
|
||||
def get_manager_name(cls, model):
|
||||
return cls._manager_names[model]
|
||||
|
||||
@classmethod
|
||||
def register(cls, model, permissions):
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
|
||||
cls._registry.setdefault(model, [])
|
||||
for permission in permissions:
|
||||
cls._registry[model].append(permission)
|
||||
|
||||
AccessControlList = apps.get_model(
|
||||
app_label='acls', model_name='AccessControlList'
|
||||
)
|
||||
|
||||
model.add_to_class(
|
||||
name='acls', value=GenericRelation(AccessControlList)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def register_function(cls, model, function):
|
||||
cls._functions[model] = function
|
||||
@@ -104,3 +123,7 @@ class ModelPermission(object):
|
||||
@classmethod
|
||||
def register_inheritance(cls, model, related):
|
||||
cls._inheritances[model] = related
|
||||
|
||||
@classmethod
|
||||
def register_manager(cls, model, manager_name):
|
||||
cls._manager_names[model] = manager_name
|
||||
|
||||
@@ -45,8 +45,8 @@ class AccessControlListManager(models.Manager):
|
||||
# 4: No related field, but has an inherited related field, solved by
|
||||
# recursion, branches to #2 or #3.
|
||||
# 5: Inherited field of a related field
|
||||
# -- Not addressed yet --
|
||||
# 6: Inherited field of a related field that is Generic Foreign Key
|
||||
# -- Not addressed yet --
|
||||
# 7: Has a related function
|
||||
result = []
|
||||
|
||||
@@ -58,10 +58,28 @@ class AccessControlListManager(models.Manager):
|
||||
if isinstance(related_field, GenericForeignKey):
|
||||
# Case 3: Generic Foreign Key, multiple ContentTypes + object
|
||||
# id combinations
|
||||
# Also handles case #6 using the parent related field
|
||||
# reference template.
|
||||
|
||||
# Craft a double underscore reference to a previous related
|
||||
# field in the case where multiple related fields are
|
||||
# associated.
|
||||
# Example: object_layer__content_type
|
||||
recuisive_related_reference = '__'.join(related_field_name.split('__')[0:-1])
|
||||
|
||||
# If there is at least one parent related field we add a
|
||||
# double underscore to make it a valid filter template.
|
||||
if recuisive_related_reference:
|
||||
recuisive_related_reference = '{}__'.format(recuisive_related_reference)
|
||||
|
||||
content_type_object_id_queryset = queryset.annotate(
|
||||
ct_fk_combination=Concat(
|
||||
related_field.ct_field, Value('-'),
|
||||
related_field.fk_field, output_field=CharField()
|
||||
'{}{}'.format(
|
||||
recuisive_related_reference, related_field.ct_field
|
||||
), Value('-'),
|
||||
'{}{}'.format(
|
||||
recuisive_related_reference, related_field.fk_field
|
||||
), output_field=CharField()
|
||||
)
|
||||
).values('ct_fk_combination')
|
||||
|
||||
@@ -75,8 +93,7 @@ class AccessControlListManager(models.Manager):
|
||||
ct_fk_combination__in=content_type_object_id_queryset
|
||||
).values('object_id')
|
||||
|
||||
field_lookup = 'object_id__in'
|
||||
|
||||
field_lookup = '{}object_id__in'.format(recuisive_related_reference)
|
||||
result.append(Q(**{field_lookup: acl_filter}))
|
||||
else:
|
||||
# Case 2: Related field of a single type, single ContentType,
|
||||
@@ -97,6 +114,7 @@ class AccessControlListManager(models.Manager):
|
||||
|
||||
# Case 5: Related field, has an inherited related field itself
|
||||
# Bubble up permssion check
|
||||
# Recurse and reduce
|
||||
# TODO: Add relationship support: OR or AND
|
||||
# TODO: OR for document pages, version, doc, and types
|
||||
# TODO: AND for new cabinet levels ACLs
|
||||
@@ -200,14 +218,11 @@ class AccessControlListManager(models.Manager):
|
||||
|
||||
return result
|
||||
|
||||
def check_access(self, obj, permissions, user, manager=None):
|
||||
def check_access(self, obj, permissions, user):
|
||||
# Allow specific managers for models that have more than one
|
||||
# for example the Document model when checking for access for a trashed
|
||||
# document.
|
||||
|
||||
if manager:
|
||||
source_queryset = manager.all()
|
||||
else:
|
||||
meta = getattr(obj, '_meta', None)
|
||||
|
||||
if not meta:
|
||||
@@ -219,9 +234,10 @@ class AccessControlListManager(models.Manager):
|
||||
)
|
||||
return True
|
||||
else:
|
||||
source_queryset = obj._meta.default_manager.all()
|
||||
manager = ModelPermission.get_manager(model=obj._meta.model)
|
||||
source_queryset = manager.all()
|
||||
|
||||
restricted_queryset = obj._meta.default_manager.none()
|
||||
restricted_queryset = manager.none()
|
||||
for permission in permissions:
|
||||
# Default relationship betweens permissions is OR
|
||||
# TODO: Add support for AND relationship
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals
|
||||
from rest_framework import status
|
||||
|
||||
from mayan.apps.permissions.tests.literals import TEST_ROLE_LABEL
|
||||
from mayan.apps.rest_api.tests import BaseAPITestCase
|
||||
from mayan.apps.rest_api.tests.base import BaseAPITestCase
|
||||
|
||||
from ..models import AccessControlList
|
||||
from ..permissions import permission_acl_edit, permission_acl_view
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from mayan.apps.common.tests import BaseTestCase
|
||||
from mayan.apps.common.tests.base import BaseTestCase
|
||||
|
||||
from ..classes import ModelPermission
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from mayan.apps.common.tests import GenericViewTestCase
|
||||
from mayan.apps.common.tests.base import GenericViewTestCase
|
||||
|
||||
from ..links import (
|
||||
link_acl_delete, link_acl_list, link_acl_create, link_acl_permissions
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import models
|
||||
|
||||
from mayan.apps.common.tests import BaseTestCase
|
||||
from mayan.apps.common.tests.base import BaseTestCase
|
||||
|
||||
from ..classes import ModelPermission
|
||||
from ..models import AccessControlList
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from mayan.apps.common.tests import GenericViewTestCase
|
||||
from mayan.apps.common.tests.base import GenericViewTestCase
|
||||
|
||||
from ..models import AccessControlList
|
||||
from ..permissions import permission_acl_edit, permission_acl_view
|
||||
|
||||
@@ -16,7 +16,6 @@ from mayan.apps.permissions.models import Role
|
||||
from .classes import ModelPermission
|
||||
from .permissions import permission_acl_edit
|
||||
|
||||
__all__ = ('GrantAccessAction', 'RevokeAccessAction')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -57,7 +56,7 @@ class GrantAccessAction(WorkflowAction):
|
||||
}
|
||||
}
|
||||
field_order = ('content_type', 'object_id', 'roles', 'permissions')
|
||||
label = _('Grant access')
|
||||
label = _('Grant object access')
|
||||
widgets = {
|
||||
'content_type': {
|
||||
'class': 'django.forms.widgets.Select', 'kwargs': {
|
||||
@@ -140,7 +139,7 @@ class GrantAccessAction(WorkflowAction):
|
||||
|
||||
|
||||
class RevokeAccessAction(GrantAccessAction):
|
||||
label = _('Revoke access')
|
||||
label = _('Revoke object access')
|
||||
|
||||
def execute(self, context):
|
||||
self.get_execute_data()
|
||||
|
||||
@@ -4,6 +4,7 @@ from django.template.loader import get_template
|
||||
|
||||
|
||||
class IconDriver(object):
|
||||
context = {}
|
||||
_registry = {}
|
||||
|
||||
@classmethod
|
||||
@@ -14,6 +15,17 @@ class IconDriver(object):
|
||||
def register(cls, driver_class):
|
||||
cls._registry[driver_class.name] = driver_class
|
||||
|
||||
def get_context(self):
|
||||
return self.context
|
||||
|
||||
def render(self, extra_context=None):
|
||||
context = self.get_context()
|
||||
if extra_context:
|
||||
context.update(extra_context)
|
||||
return get_template(template_name=self.template_name).render(
|
||||
context=context
|
||||
)
|
||||
|
||||
|
||||
class FontAwesomeDriver(IconDriver):
|
||||
name = 'fontawesome'
|
||||
@@ -22,10 +34,8 @@ class FontAwesomeDriver(IconDriver):
|
||||
def __init__(self, symbol):
|
||||
self.symbol = symbol
|
||||
|
||||
def render(self):
|
||||
return get_template(template_name=self.template_name).render(
|
||||
context={'symbol': self.symbol}
|
||||
)
|
||||
def get_context(self):
|
||||
return {'symbol': self.symbol}
|
||||
|
||||
|
||||
class FontAwesomeDualDriver(IconDriver):
|
||||
@@ -36,9 +46,8 @@ class FontAwesomeDualDriver(IconDriver):
|
||||
self.primary_symbol = primary_symbol
|
||||
self.secondary_symbol = secondary_symbol
|
||||
|
||||
def render(self):
|
||||
return get_template(template_name=self.template_name).render(
|
||||
context={
|
||||
def get_context(self):
|
||||
return {
|
||||
'data': (
|
||||
{
|
||||
'class': 'fas fa-circle',
|
||||
@@ -52,7 +61,6 @@ class FontAwesomeDualDriver(IconDriver):
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class FontAwesomeCSSDriver(IconDriver):
|
||||
@@ -62,10 +70,8 @@ class FontAwesomeCSSDriver(IconDriver):
|
||||
def __init__(self, css_classes):
|
||||
self.css_classes = css_classes
|
||||
|
||||
def render(self):
|
||||
return get_template(template_name=self.template_name).render(
|
||||
context={'css_classes': self.css_classes}
|
||||
)
|
||||
def get_context(self):
|
||||
return {'css_classes': self.css_classes}
|
||||
|
||||
|
||||
class FontAwesomeMasksDriver(IconDriver):
|
||||
@@ -75,23 +81,23 @@ class FontAwesomeMasksDriver(IconDriver):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def render(self):
|
||||
return get_template(template_name=self.template_name).render(
|
||||
context={'data': self.data}
|
||||
)
|
||||
def get_context(self):
|
||||
return {'data': self.data}
|
||||
|
||||
|
||||
class FontAwesomeLayersDriver(IconDriver):
|
||||
name = 'fontawesome-layers'
|
||||
template_name = 'appearance/icons/font_awesome_layers.html'
|
||||
|
||||
def __init__(self, data):
|
||||
def __init__(self, data, shadow_class=None):
|
||||
self.data = data
|
||||
self.shadow_class = shadow_class
|
||||
|
||||
def render(self):
|
||||
return get_template(template_name=self.template_name).render(
|
||||
context={'data': self.data}
|
||||
)
|
||||
def get_context(self):
|
||||
return {
|
||||
'data': self.data,
|
||||
'shadow_class': self.shadow_class,
|
||||
}
|
||||
|
||||
|
||||
class Icon(object):
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
DEFAULT_MAXIMUM_TITLE_LENGTH = 120
|
||||
DEFAULT_MESSAGE_POSITION = 'top-right'
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.smart_settings.classes import Namespace
|
||||
|
||||
from .literals import DEFAULT_MAXIMUM_TITLE_LENGTH
|
||||
from .literals import DEFAULT_MAXIMUM_TITLE_LENGTH, DEFAULT_MESSAGE_POSITION
|
||||
|
||||
namespace = Namespace(label=_('Appearance'), name='appearance')
|
||||
|
||||
@@ -15,3 +15,11 @@ setting_max_title_length = namespace.add_setting(
|
||||
'title.'
|
||||
)
|
||||
)
|
||||
setting_message_position = namespace.add_setting(
|
||||
default=DEFAULT_MESSAGE_POSITION,
|
||||
global_name='APPEARANCE_MESSAGE_POSITION', help_text=_(
|
||||
'Position where the system message will be displayed. Options are: '
|
||||
'top-left, top-center, top-right, bottom-left, bottom-center, '
|
||||
'bottom-right.'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 70px;
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
@@ -70,7 +70,8 @@ img.lazy-load-carousel {
|
||||
}
|
||||
|
||||
.label-tag {
|
||||
text-shadow: 0px 0px 2px #000
|
||||
text-shadow: 0px 0px 2px #000;
|
||||
box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.fancybox-nav span {
|
||||
@@ -88,19 +89,17 @@ hr {
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
border-top: 2px solid rgba(255, 255, 255, 0.7);
|
||||
border-left: 2px solid rgba(255, 255, 255, 0.7);
|
||||
border-right: 2px solid rgba(0, 0, 0, 0.7);
|
||||
border-bottom: 2px solid rgba(0, 0, 0, 0.7);
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.5);
|
||||
margin-bottom: 15px;
|
||||
white-space: normal;
|
||||
min-height: 120px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.btn-block .fa {
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
|
||||
padding-top: 20px;
|
||||
text-shadow: 1px 1px 3px rgba(0, 0, 0, 1);
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.radio ul li {
|
||||
@@ -112,14 +111,10 @@ a i {
|
||||
}
|
||||
|
||||
.dashboard-widget {
|
||||
box-shadow: 1px 1px 1px rgba(0,0,0,0.3);
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.7);
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.dashboard-widget .panel-heading i {
|
||||
text-shadow: 1px 1px 1px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.dashboard-widget-icon {
|
||||
font-size: 200%;
|
||||
}
|
||||
@@ -170,7 +165,7 @@ a i {
|
||||
}
|
||||
.navbar-collapse {
|
||||
border-top: 1px solid transparent;
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.navbar-fixed-top {
|
||||
top: 0;
|
||||
@@ -213,6 +208,22 @@ a i {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.source-column-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.panel-highlighted {
|
||||
box-shadow: 0px 0px 3px #18bc9c, 10px 10px 20px #000000;
|
||||
}
|
||||
|
||||
.panel-highlighted:hover {
|
||||
box-shadow: 0px 0px 3px #18bc9c, 10px 10px 20px #000000, 0px 0px 8px #000000;
|
||||
}
|
||||
|
||||
.panel-item:not(.panel-highlighted):hover {
|
||||
box-shadow: 0px 0px 8px #000000;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
@media (min-width:1200px) {
|
||||
.container-fluid {
|
||||
@@ -242,14 +253,6 @@ a i {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.thin_border {
|
||||
border: 1px solid black;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
|
||||
.thin_border-thumbnail {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
@@ -259,10 +262,18 @@ a i {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
/* Must go after .thin_border-thumbnail */
|
||||
.thin_border {
|
||||
border: 1px solid black;
|
||||
display: inline;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
#ajax-spinner {
|
||||
position: fixed;
|
||||
top: 12px;
|
||||
right: 10px;
|
||||
top: 16px;
|
||||
left: 10px;
|
||||
z-index: 9999;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
@@ -328,7 +339,7 @@ a i {
|
||||
.main {
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
/*margin-left: 210px;*/
|
||||
margin-left: 210px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,6 +422,141 @@ a i {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Top navigation
|
||||
* Hide default border to remove 1px line.
|
||||
*/
|
||||
.navbar-fixed-top {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
||||
/* menu_main */
|
||||
/* Hide for mobile, show later */
|
||||
|
||||
#menu-main {
|
||||
display: none;
|
||||
background-color: #2c3e50;
|
||||
border-right: 1px solid #18bc9c;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding-top: 10px;
|
||||
position: fixed;
|
||||
top: 51px;
|
||||
width: 210px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
#menu-main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
text-align: center;
|
||||
width: 210px;
|
||||
}
|
||||
}
|
||||
|
||||
.main .page-header {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
margin-right: 0px;
|
||||
margin-left: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#accordion-sidebar a {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
#accordion-sidebar a[aria-expanded="true"] {
|
||||
background: #1a242f;
|
||||
}
|
||||
|
||||
#accordion-sidebar .panel {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
#accordion-sidebar a {
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#accordion-sidebar .panel-heading {
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#accordion-sidebar .panel-heading:hover {
|
||||
background-color: #517394;
|
||||
}
|
||||
|
||||
#accordion-sidebar > .panel > div > .panel-body > ul > li > a:hover {
|
||||
background-color: #517394;
|
||||
}
|
||||
|
||||
#accordion-sidebar > .panel > div > .panel-body > ul > li.active {
|
||||
background: #1a242f;
|
||||
}
|
||||
|
||||
#accordion-sidebar .panel-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#accordion-sidebar .panel-body {
|
||||
font-size: 13px;
|
||||
border: 0px;
|
||||
background-color: #2c3e50;
|
||||
padding-top: 5px;
|
||||
padding-left: 20px;
|
||||
padding-right: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
#accordion-sidebar .panel-body li {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#accordion-sidebar .panel-body a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
.navbar-fixed-top {
|
||||
box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, .3);
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 12px;
|
||||
padding-right: 15px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
#body-plain {
|
||||
padding-top: 0px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* jstree - cabinets */
|
||||
#jstree {
|
||||
max-width: 100%;
|
||||
@@ -418,5 +564,4 @@ a i {
|
||||
font: 11px Verdana, sans-serif;
|
||||
padding: 0px;
|
||||
padding-bottom: 10px; /* Padding for scrollbar */
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ var MayanAppClass = MayanApp;
|
||||
|
||||
var partialNavigation = new PartialNavigation({
|
||||
initialURL: initialURL,
|
||||
disabledAnchorClasses: ['disabled'],
|
||||
disabledAnchorClasses: [
|
||||
'btn-multi-item-action', 'disabled', 'pagination-disabled'
|
||||
],
|
||||
excludeAnchorClasses: ['fancybox', 'new_window', 'non-ajax'],
|
||||
formBeforeSerializeCallbacks: [MayanApp.MultiObjectFormProcess],
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ class MayanApp {
|
||||
constructor (options) {
|
||||
var self = this;
|
||||
|
||||
options = options || {
|
||||
this.options = options || {
|
||||
ajaxMenusOptions: []
|
||||
}
|
||||
|
||||
@@ -17,28 +17,44 @@ class MayanApp {
|
||||
|
||||
// Class methods and variables
|
||||
|
||||
static MultiObjectFormProcess ($form, options) {
|
||||
/*
|
||||
* ajaxForm callback to add the external item checkboxes to the
|
||||
* submitted form
|
||||
*/
|
||||
static countChecked() {
|
||||
var checkCount = $('.check-all-slave:checked').length;
|
||||
|
||||
if ($form.hasClass('form-multi-object-action')) {
|
||||
// Turn form data into an object
|
||||
var formArray = $form.serializeArray().reduce(function (obj, item) {
|
||||
obj[item.name] = item.value;
|
||||
return obj;
|
||||
}, {});
|
||||
if (checkCount) {
|
||||
$('#multi-item-title').hide();
|
||||
$('#multi-item-actions').show();
|
||||
} else {
|
||||
$('#multi-item-title').show();
|
||||
$('#multi-item-actions').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Add all checked checkboxes to the form data
|
||||
$('.form-multi-object-action-checkbox:checked').each(function() {
|
||||
var $this = $(this);
|
||||
formArray[$this.attr('name')] = $this.attr('value');
|
||||
static setupMultiItemActions () {
|
||||
$('body').on('change', '.check-all-slave', function () {
|
||||
MayanApp.countChecked();
|
||||
});
|
||||
|
||||
// Set the form data as the data to send
|
||||
options.data = formArray;
|
||||
$('body').on('click', '.btn-multi-item-action', function (event) {
|
||||
var id_list = [];
|
||||
$('.check-all-slave:checked').each(function (index, value) {
|
||||
//Split the name (ie:"pk_200") and extract only the ID
|
||||
id_list.push(value.name.split('_')[1]);
|
||||
});
|
||||
event.preventDefault();
|
||||
partialNavigation.setLocation(
|
||||
$(this).attr('href') + '?id_list=' + id_list.join(',')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static setupNavBarState () {
|
||||
$('body').on('click', '.a-main-menu-accordion-link', function (event) {
|
||||
$('.a-main-menu-accordion-link').each(function (index, value) {
|
||||
$(this).parent().removeClass('active');
|
||||
});
|
||||
|
||||
$(this).parent().addClass('active');
|
||||
});
|
||||
}
|
||||
|
||||
static updateNavbarState () {
|
||||
@@ -46,8 +62,10 @@ class MayanApp {
|
||||
var uriFragment = uri.fragment();
|
||||
$('.a-main-menu-accordion-link').each(function (index, value) {
|
||||
if (value.pathname === uriFragment) {
|
||||
$(this).closest('.collapse').addClass('in').parent().find('.collapsed').removeClass('collapsed').attr('aria-expanded', 'true');
|
||||
$(this).parent().addClass('active');
|
||||
var $this = $(this);
|
||||
|
||||
$this.closest('.collapse').addClass('in').parent().find('.collapsed').removeClass('collapsed').attr('aria-expanded', 'true');
|
||||
$this.parent().addClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -92,7 +110,7 @@ class MayanApp {
|
||||
'closeButton': true,
|
||||
'debug': false,
|
||||
'newestOnTop': true,
|
||||
'positionClass': 'toast-top-right',
|
||||
'positionClass': 'toast-' + this.options.messagePosition,
|
||||
'preventDuplicates': false,
|
||||
'onclick': null,
|
||||
'showDuration': '300',
|
||||
@@ -162,17 +180,19 @@ class MayanApp {
|
||||
var self = this;
|
||||
|
||||
this.setupAJAXSpinner();
|
||||
this.setupAutoSubmit();
|
||||
this.setupBodyAdjust();
|
||||
this.setupFormHotkeys();
|
||||
this.setupFullHeightResizing();
|
||||
this.setupItemsSelector();
|
||||
MayanApp.setupMultiItemActions();
|
||||
this.setupNavbarCollapse();
|
||||
MayanApp.setupNavBarState();
|
||||
this.setupNewWindowAnchor();
|
||||
$.each(this.ajaxMenusOptions, function(index, value) {
|
||||
value.app = self;
|
||||
app.doRefreshAJAXMenu(value);
|
||||
});
|
||||
this.setupPanelSelection();
|
||||
partialNavigation.initialize();
|
||||
}
|
||||
|
||||
@@ -196,14 +216,6 @@ class MayanApp {
|
||||
});
|
||||
}
|
||||
|
||||
setupAutoSubmit () {
|
||||
$('body').on('change', '.select-auto-submit', function () {
|
||||
if ($(this).val()) {
|
||||
$(this.form).trigger('submit');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupBodyAdjust () {
|
||||
var self = this;
|
||||
|
||||
@@ -242,9 +254,22 @@ class MayanApp {
|
||||
app.lastChecked = null;
|
||||
|
||||
$('body').on('click', '.check-all', function (event) {
|
||||
var $this = $(this);
|
||||
var checked = $(event.target).prop('checked');
|
||||
var $checkBoxes = $('.check-all-slave');
|
||||
|
||||
if (checked === undefined) {
|
||||
checked = $this.data('checked');
|
||||
checked = !checked;
|
||||
$this.data('checked', checked);
|
||||
|
||||
if (checked) {
|
||||
$this.find('[data-fa-i2svg]').addClass($this.data('icon-checked')).removeClass($this.data('icon-unchecked'));
|
||||
} else {
|
||||
$this.find('[data-fa-i2svg]').addClass($this.data('icon-unchecked')).removeClass($this.data('icon-checked'));
|
||||
}
|
||||
}
|
||||
|
||||
$checkBoxes.prop('checked', checked);
|
||||
$checkBoxes.trigger('change');
|
||||
});
|
||||
@@ -290,6 +315,58 @@ class MayanApp {
|
||||
});
|
||||
}
|
||||
|
||||
setupPanelSelection () {
|
||||
var app = this;
|
||||
|
||||
// Setup panel highlighting on check
|
||||
$('body').on('change', '.check-all-slave', function (event) {
|
||||
var checked = $(event.target).prop('checked');
|
||||
if (checked) {
|
||||
$(this).closest('.panel-item').addClass('panel-highlighted');
|
||||
} else {
|
||||
$(this).closest('.panel-item').removeClass('panel-highlighted');
|
||||
}
|
||||
});
|
||||
|
||||
$('body').on('click', '.panel-item', function (event) {
|
||||
var $this = $(this);
|
||||
var targetSrc = $(event.target).prop('src');
|
||||
var targetHref = $(event.target).prop('href');
|
||||
var targetIsButton = event.target.tagName === 'BUTTON';
|
||||
var lastChecked = null;
|
||||
|
||||
if ((targetSrc === undefined) && (targetHref === undefined) && (targetIsButton === false)) {
|
||||
var $checkbox = $this.find('.check-all-slave');
|
||||
var checked = $checkbox.prop('checked');
|
||||
|
||||
if (checked) {
|
||||
$checkbox.prop('checked', '');
|
||||
$checkbox.trigger('change');
|
||||
} else {
|
||||
$checkbox.prop('checked', 'checked');
|
||||
$checkbox.trigger('change');
|
||||
}
|
||||
|
||||
if(!app.lastChecked) {
|
||||
app.lastChecked = $checkbox;
|
||||
}
|
||||
|
||||
if (event.shiftKey) {
|
||||
var $checkBoxes = $('.check-all-slave');
|
||||
|
||||
var start = $checkBoxes.index($checkbox);
|
||||
var end = $checkBoxes.index(app.lastChecked);
|
||||
|
||||
$checkBoxes.slice(
|
||||
Math.min(start, end), Math.max(start, end) + 1
|
||||
).prop('checked', app.lastChecked.prop('checked')).trigger('change');
|
||||
}
|
||||
app.lastChecked = $checkbox;
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupScrollView () {
|
||||
$('.scrollable').scrollview();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
{% block content_plain %}{% endblock %}
|
||||
{% else %}
|
||||
<div class="">
|
||||
|
||||
{% navigation_resolve_menus names='facet,list facet' sort_results=True as facet_menus_link_results %}
|
||||
|
||||
<div class="row zero-margin">
|
||||
@@ -43,7 +44,7 @@
|
||||
{% if settings_changed %}
|
||||
<div class="alert alert-dismissible alert-warning">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
<p><strong>{% trans 'Warning' %}</strong> {% trans 'Settings updated, restart your installation for changes to take proper effect.' %}</p>
|
||||
<p><strong>{% trans 'Warning' %}</strong> {% trans 'Settings updated, restart your installation and refresh your browser for changes to take effect.' %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -138,6 +139,9 @@
|
||||
},
|
||||
{% endfor %}
|
||||
];
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
})
|
||||
</script>
|
||||
{% block javascript %}{% endblock %}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<body id="body-plain">
|
||||
{% block content_plain %}{% endblock %}
|
||||
|
||||
<script src="{% static 'appearance/node_modules/jquery/dist/jquery.min.js' %}" type="text/javascript"></script>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
{% for field in form.visible_fields %}
|
||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||
<div class="form-group {% if field.errors %}has-error{% endif %} {{ form_field_css_classes }}">
|
||||
{# We display the label then the field for all except checkboxes #}
|
||||
{% if field|widget_type != 'checkboxinput' and not field.field.widget.attrs.hidden %}
|
||||
{% if not hide_labels %}{{ field.label_tag }}{% if field.field.required and not read_only %} ({% trans 'required' %}){% endif %}{% endif %}
|
||||
|
||||
@@ -11,41 +11,9 @@
|
||||
{% include 'appearance/no_results.html' %}
|
||||
</div>
|
||||
{% else %}
|
||||
<h4>
|
||||
{% if page_obj %}
|
||||
{% if page_obj.paginator.num_pages != 1 %}
|
||||
{% blocktrans with page_obj.start_index as start and page_obj.end_index as end and page_obj.paginator.object_list|length as total and page_obj.number as page_number and page_obj.paginator.num_pages as total_pages %}Total ({{ start }} - {{ end }} out of {{ total }}) (Page {{ page_number }} of {{ total_pages }}){% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with page_obj.paginator.object_list|length as total %}Total: {{ total }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% blocktrans with object_list|length as total %}Total: {{ total }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
</h4>
|
||||
<hr>
|
||||
{% include "appearance/list_header.html" %}
|
||||
{% navigation_resolve_menu name='multi item' sort_results=True source=object_list.0 as links_multi_menus_results %}
|
||||
<div class="well center-block">
|
||||
<div class="clearfix">
|
||||
<div class="pull-right">
|
||||
<form action="{% url 'common:multi_object_action_view' %}" class="form-multi-object-action" method="get">
|
||||
{% if object_list %}
|
||||
{% if not hide_multi_item_actions %}
|
||||
{% get_multi_item_links_form object_list %}
|
||||
{% endif %}
|
||||
{% if multi_item_actions %}
|
||||
<fieldset style="margin-top: -10px;">
|
||||
<input class="check-all" type="checkbox"/>
|
||||
{{ multi_item_form }}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if object_list %}
|
||||
<hr style="border-bottom: 1px solid lightgrey;">
|
||||
{% endif %}
|
||||
|
||||
<div class="row row-items">
|
||||
{% for object in object_list %}
|
||||
<div class="{{ column_class|default:'col-xs-12 col-sm-4 col-md-3 col-lg-2' }}">
|
||||
@@ -53,9 +21,9 @@
|
||||
<div class="panel-heading">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label for="id_indexes_0">
|
||||
{% if multi_item_actions %}
|
||||
<input class="form-multi-object-action-checkbox check-all-slave checkbox" type="checkbox" name="pk_{{ object.pk }}" />
|
||||
<label for="id_indexes_0" style="cursor: auto;">
|
||||
{% if links_multi_menus_results %}
|
||||
<input class="form-multi-object-action-checkbox check-all-slave checkbox" name="pk_{{ object.pk }}" style="cursor: pointer;" type="checkbox" />
|
||||
{% endif %}
|
||||
|
||||
<span style="color: white; word-break: break-all; overflow-wrap: break-word;">
|
||||
@@ -68,13 +36,8 @@
|
||||
{% else %}
|
||||
{% navigation_get_source_columns source=object only_identifier=True as source_column %}
|
||||
{% navigation_source_column_resolve column=source_column as column_value %}
|
||||
|
||||
{% if source_column.is_attribute_absolute_url or source_column.is_object_absolute_url %}
|
||||
<a href="{% navigation_source_column_get_absolute_url source_column=source_column obj=object %}">{{ column_value }}</a>
|
||||
{% else %}
|
||||
{{ column_value }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -82,11 +45,10 @@
|
||||
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
{% if not hide_columns %}
|
||||
{% navigation_get_source_columns source=object exclude_identifier=True as 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 %}
|
||||
{% endif %}
|
||||
|
||||
@@ -136,7 +98,6 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% include 'pagination/pagination.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% load appearance_tags %}
|
||||
{% load common_tags %}
|
||||
{% load navigation_tags %}
|
||||
|
||||
@@ -11,44 +12,16 @@
|
||||
{% include 'appearance/no_results.html' %}
|
||||
</div>
|
||||
{% else %}
|
||||
<h4>
|
||||
{% if page_obj %}
|
||||
{% if page_obj.paginator.num_pages != 1 %}
|
||||
{% blocktrans with page_obj.start_index as start and page_obj.end_index as end and page_obj.paginator.object_list|length as total and page_obj.number as page_number and page_obj.paginator.num_pages as total_pages %}Total ({{ start }} - {{ end }} out of {{ total }}) (Page {{ page_number }} of {{ total_pages }}){% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with page_obj.paginator.object_list|length as total %}Total: {{ total }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% blocktrans with object_list|length as total %}Total: {{ total }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
</h4>
|
||||
<hr>
|
||||
|
||||
{% include "appearance/list_header.html" %}
|
||||
{% navigation_resolve_menu name='multi item' sort_results=True source=object_list.0 as links_multi_menus_results %}
|
||||
<div class="well center-block">
|
||||
<div class="clearfix">
|
||||
<div class="pull-right">
|
||||
<form action="{% url 'common:multi_object_action_view' %}" class="form-multi-object-action" method="get">
|
||||
{% if object_list %}
|
||||
{% if not hide_multi_item_actions %}
|
||||
{% get_multi_item_links_form object_list %}
|
||||
{% endif %}
|
||||
{% if multi_item_actions %}
|
||||
<fieldset style="margin-top: -10px; margin-bottom: 10px;">
|
||||
{{ multi_item_form }}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed table-striped">
|
||||
<tbody>
|
||||
{% if not hide_header %}
|
||||
<tr>
|
||||
{% if multi_item_actions %}
|
||||
<th class="first"><input class="checkbox check-all" type="checkbox" /></th>
|
||||
{% if links_multi_menus_results %}
|
||||
<th class="first"></th>
|
||||
{% endif %}
|
||||
|
||||
{% if not hide_object %}
|
||||
@@ -57,32 +30,46 @@
|
||||
{% navigation_get_source_columns source=object_list only_identifier=True as source_column %}
|
||||
{% if source_column %}
|
||||
<th>
|
||||
<span style="white-space: nowrap">
|
||||
{% if source_column.is_sortable %}
|
||||
<a href="{% navigation_get_sort_field_querystring column=source_column %}">{{ source_column.label }}
|
||||
<a href="{% navigation_get_sort_field_querystring column=source_column %}">{{ source_column.label }}</a>
|
||||
{% if source_column.get_sort_field == sort_field %}
|
||||
{% if icon_sort %}{{ icon_sort.render }}{% endif %}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ source_column.label }}
|
||||
{% endif %}
|
||||
|
||||
{% if source_column.help_text %}
|
||||
<span data-toggle="tooltip" data-placement="bottom" title="{{ source_column.help_text }}">
|
||||
{% get_icon icon_path='mayan.apps.navigation.icons.icon_source_column_help_text' %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</th>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if not hide_columns %}
|
||||
{% navigation_get_source_columns source=object_list exclude_identifier=True as source_columns %}
|
||||
{% for column in source_columns %}
|
||||
{% for source_column in source_columns %}
|
||||
<th>
|
||||
{% if column.is_sortable %}
|
||||
<a href="{% navigation_get_sort_field_querystring column=column %}">{{ column.label }}
|
||||
{% if column.get_sort_field == sort_field %}
|
||||
<span style="white-space: nowrap">
|
||||
{% if source_column.is_sortable %}
|
||||
<a href="{% navigation_get_sort_field_querystring column=source_column %}">{{ source_column.label }}</a>
|
||||
{% if source_column.get_sort_field == sort_field %}
|
||||
{% if icon_sort %}{{ icon_sort.render }}{% endif %}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ column.label }}
|
||||
{{ source_column.label }}
|
||||
{% endif %}
|
||||
|
||||
{% if source_column.help_text %}
|
||||
<span data-toggle="tooltip" data-placement="bottom" title="{{ source_column.help_text }}">
|
||||
{% get_icon icon_path='mayan.apps.navigation.icons.icon_source_column_help_text' %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</th>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
@@ -99,9 +86,9 @@
|
||||
{% for object in object_list %}
|
||||
<tr>
|
||||
|
||||
{% if multi_item_actions %}
|
||||
{% if links_multi_menus_results %}
|
||||
<td>
|
||||
<input type="checkbox" class="form-multi-object-action-checkbox check-all-slave checkbox" name="pk_{{ object.pk }}" value="" />
|
||||
<input class="form-multi-object-action-checkbox check-all-slave checkbox" name="pk_{{ object.pk }}" type="checkbox" value="" />
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
@@ -112,11 +99,7 @@
|
||||
{% navigation_source_column_resolve column=source_column as column_value %}
|
||||
{% if column_value %}
|
||||
<td>
|
||||
{% if source_column.is_attribute_absolute_url or source_column.is_object_absolute_url %}
|
||||
<a href="{% navigation_source_column_get_absolute_url source_column=source_column obj=object %}">{{ column_value }}</a>
|
||||
{% else %}
|
||||
{{ column_value }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -170,7 +153,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% include 'pagination/pagination.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<span class="fa-layers fa-fw" style="margin-right: 7px;">
|
||||
{% if enable_shadow %}
|
||||
<i class="{{ shadow_class }}" data-fa-transform="right-1 down-2" style="color:rgba(0, 0, 0, 0.3); stroke: rgba(255, 255, 255, 0.3); stroke-width: 20;"></i>
|
||||
{% endif %}
|
||||
{% for entry in data %}
|
||||
<i class="{{ entry.class }}" data-fa-transform="{{ entry.transform }}" data-fa-mask="{{ entry.mask }}"></i>
|
||||
{% endfor %}
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
<i class="fa fa-{{ symbol }}" style="padding-right: 5px; width: auto;"></i>
|
||||
{% if enable_shadow %}
|
||||
<span class="fa-layers fa-fw" >
|
||||
<i class="fa fa-{{ symbol }}" data-fa-transform="right-1 down-2" style="color:rgba(0, 0, 0, 0.3);stroke: rgba(255, 255, 255, 0.3); stroke-width: 20;"></i>
|
||||
<i class="fa fa-{{ symbol }}"></i>
|
||||
</span>
|
||||
{% else %}
|
||||
<i class="fa fa-{{ symbol }}" style="padding-right: 5px; width: auto;"></i>
|
||||
{% endif %}
|
||||
|
||||
28
mayan/apps/appearance/templates/appearance/list_header.html
Normal file
28
mayan/apps/appearance/templates/appearance/list_header.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% load common_tags %}
|
||||
{% load navigation_tags %}
|
||||
|
||||
{% if object_list %}
|
||||
<h4>
|
||||
{% if page_obj %}
|
||||
{% if page_obj.paginator.num_pages != 1 %}
|
||||
{% blocktrans with page_obj.start_index as start and page_obj.end_index as end and page_obj.paginator.object_list|length as total and page_obj.number as page_number and page_obj.paginator.num_pages as total_pages %}Total ({{ start }} - {{ end }} out of {{ total }}) (Page {{ page_number }} of {{ total_pages }}){% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with page_obj.paginator.object_list|length as total %}Total: {{ total }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% blocktrans with object_list|length as total %}Total: {{ total }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
</h4>
|
||||
<hr>
|
||||
|
||||
{% if not hide_multi_item_actions %}
|
||||
{% navigation_resolve_menu name='multi item' sort_results=True source=object_list.0 as links_multi_menus_results %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="clearfix">
|
||||
{% include 'appearance/list_toolbar.html' %}
|
||||
</div>
|
||||
90
mayan/apps/appearance/templates/appearance/list_toolbar.html
Normal file
90
mayan/apps/appearance/templates/appearance/list_toolbar.html
Normal file
@@ -0,0 +1,90 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% load common_tags %}
|
||||
{% load navigation_tags %}
|
||||
|
||||
{% if is_paginated or links_multi_menus_results %}
|
||||
<div class="well center-block toolbar">
|
||||
{% endif %}
|
||||
|
||||
{% if links_multi_menus_results %}
|
||||
<div class="pull-left">
|
||||
<div class="btn-toolbar" role="toolbar" style="margin-right: 10px;">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default btn-sm check-all" data-checked=false data-icon-checked="fa fa-check-square" data-icon-unchecked="far fa-square" title="{% trans 'Select/Deselect all' %}">
|
||||
<i class="far fa-square"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if is_paginated %}
|
||||
<div class="pull-left">
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<div class="btn-group">
|
||||
{% if page_obj.has_previous %}
|
||||
<a class="btn btn-default btn-sm" href="?{{ page_obj.previous_page_number.querystring }}">‹‹</a>
|
||||
{% else %}
|
||||
<a class="btn btn-default btn-sm disabled" href="#">‹‹</a>
|
||||
{% endif %}
|
||||
|
||||
{% for page in page_obj.pages %}
|
||||
|
||||
{% if page %}
|
||||
|
||||
{% ifequal page page_obj.number %}
|
||||
<a class="active btn btn-default btn-sm pagination-disabled" href="#">{{ page }}</a>
|
||||
{% else %}
|
||||
<a class="btn btn-default btn-sm" href="?{{ page.querystring }}">{{ page }}</a>
|
||||
{% endifequal %}
|
||||
{% else %}
|
||||
<a class="btn btn-default btn-sm disabled" href="#">...</a>
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{% if page_obj.has_next %}
|
||||
<a class="btn btn-default btn-sm" href="?{{ page_obj.next_page_number.querystring }}">››</a>
|
||||
{% else %}
|
||||
<a class="btn btn-default btn-sm disabled" href="#">››</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if links_multi_menus_results %}
|
||||
<p class="pull-right" id="multi-item-title" style="line-height: 16px; padding-top: 8px;">{% trans 'Select items to activate bulk actions. Use Shift + click to select many.' %}</p>
|
||||
|
||||
<div class="pull-right btn-group" id="multi-item-actions" style="display: none;">
|
||||
<button aria-expanded="true" class="btn btn-danger btn-sm dropdown-toggle" data-toggle="dropdown" type="button">
|
||||
{% trans 'Bulk actions' %}
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">{% trans 'Toggle Dropdown' %}</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
{% for multi_item_menu_results in links_multi_menus_results %}
|
||||
{% for link_group in multi_item_menu_results.link_groups %}
|
||||
{% with link_group.links as object_navigation_links %}
|
||||
{% with 'true' as as_li %}
|
||||
{% with 'true' as hide_active_anchor %}
|
||||
{% with 'btn-sm btn-multi-item-action' as link_classes %}
|
||||
{% include 'navigation/generic_navigation.html' %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
|
||||
{% endfor %}
|
||||
{% if not forloop.last and link_group %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if is_paginated or links_multi_menus_results %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
70
mayan/apps/appearance/templates/appearance/menu_main.html
Normal file
70
mayan/apps/appearance/templates/appearance/menu_main.html
Normal file
@@ -0,0 +1,70 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% load navigation_tags %}
|
||||
{% load smart_settings_tags %}
|
||||
|
||||
{% load common_tags %}
|
||||
{% load navigation_tags %}
|
||||
|
||||
{% spaceless %}
|
||||
<div class="panel-group" id="accordion-sidebar" role="tablist" aria-multiselectable="true">
|
||||
{% navigation_resolve_menu name='main' as main_menus_results %}
|
||||
{% for main_menu_results in main_menus_results %}
|
||||
{% for link_group in main_menu_results.link_groups %}
|
||||
{% for link in link_group.links %}
|
||||
{% with 'active' as li_class_active %}
|
||||
{% with ' ' as link_classes %}
|
||||
{% if link|get_type == "<class 'mayan.apps.navigation.classes.Menu'>" %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="headingOne">
|
||||
<h4 class="panel-title">
|
||||
<a class="non-ajax collapsed" role="button" data-toggle="collapse" data-parent="#accordion-sidebar" href="#accordion-body-{{ forloop.counter }}" aria-expanded="false" aria-controls="collapseOne">
|
||||
<div class="pull-left">
|
||||
{% if link.icon %}
|
||||
<i class="hidden-xs hidden-sm hidden-md {{ link.icon }}"></i>
|
||||
{% endif %}
|
||||
{% if link.icon_class %}{{ link.icon_class.render }}{% endif %}
|
||||
{{ link.label }}
|
||||
</div>
|
||||
<div class="accordion-indicator pull-right"><span class="caret"></span></div>
|
||||
<div class="clearfix"></div>
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="accordion-body-{{ forloop.counter }}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingOne">
|
||||
<div class="panel-body">
|
||||
<ul class="list-unstyled">
|
||||
{% navigation_resolve_menu name=link.name as sub_menus_results %}
|
||||
{% for sub_menu_results in sub_menus_results %}
|
||||
{% for link_group in sub_menu_results.link_groups %}
|
||||
{% with '' as link_class_active %}
|
||||
{% with 'a-main-menu-accordion-link' as link_classes %}
|
||||
{% with 'true' as as_li %}
|
||||
{% with link_group.links as object_navigation_links %}
|
||||
{% include 'navigation/generic_navigation.html' %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="headingOne">
|
||||
<h4 class="panel-title">
|
||||
{% include 'navigation/generic_link_instance.html' %}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endspaceless %}
|
||||
@@ -3,10 +3,11 @@
|
||||
{% load navigation_tags %}
|
||||
{% load smart_settings_tags %}
|
||||
|
||||
{% spaceless %}
|
||||
<nav class="navbar navbar-default navbar-fixed-top">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||
<button aria-expanded="false" aria-controls="navbar" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" type="button">
|
||||
<span class="sr-only">{% trans 'Toggle navigation' %}</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
@@ -14,9 +15,10 @@
|
||||
</button>
|
||||
<a class="navbar-brand" href="{% url home_view %}">{% smart_setting 'COMMON_PROJECT_TITLE' %}</a>
|
||||
</div>
|
||||
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
{% navigation_resolve_menu name='main' as topbar_menus_results %}
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% navigation_resolve_menu name='topbar' as topbar_menus_results %}
|
||||
{% for tobpar_menu_result in topbar_menus_results %}
|
||||
{% for link_group in tobpar_menu_result.link_groups %}
|
||||
{% for link in link_group.links %}
|
||||
@@ -34,24 +36,8 @@
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
{% get_menu_links name='main' as menu_links %}
|
||||
{% for link_set in menu_links %}
|
||||
{% for link in link_set %}
|
||||
{% with 'true' as as_li %}
|
||||
{% with 'true' as hide_active_anchor %}
|
||||
{% with 'active' as li_class_active %}
|
||||
{% with 'first' as li_class_first %}
|
||||
{% with ' ' as link_classes %}
|
||||
{% include 'navigation/generic_subnavigation.html' %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% endspaceless %}
|
||||
@@ -31,8 +31,11 @@
|
||||
{% if appearance_type == 'plain' %}
|
||||
{% block content_plain %}{% endblock %}
|
||||
{% else %}
|
||||
<div id="menu-topbar">
|
||||
{% include 'appearance/menu_topbar.html' %}
|
||||
</div>
|
||||
<div id="menu-main">
|
||||
{% include 'appearance/main_menu.html' %}
|
||||
{% include 'appearance/menu_main.html' %}
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="body-spacer"></div>
|
||||
@@ -102,6 +105,7 @@
|
||||
ajaxMenusOptions: [
|
||||
{
|
||||
callback: function (options) {
|
||||
MayanApp.updateNavbarState();
|
||||
options.app.doBodyAdjust();
|
||||
},
|
||||
interval: 5000,
|
||||
@@ -109,7 +113,14 @@
|
||||
name: 'menu_main',
|
||||
url: '{% url "rest_api:template-detail" "menu_main" %}'
|
||||
},
|
||||
]
|
||||
{
|
||||
interval: 5000,
|
||||
menuSelector: '#menu-topbar',
|
||||
name: 'menu_topbar',
|
||||
url: '{% url "rest_api:template-detail" "menu_topbar" %}'
|
||||
},
|
||||
],
|
||||
messagePosition: '{% smart_setting "APPEARANCE_MESSAGE_POSITION" %}'
|
||||
});
|
||||
|
||||
var afterBaseLoad = function () {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{% if page %}
|
||||
|
||||
{% ifequal page page_obj.number %}
|
||||
<li class="active"><a class="disabled" href="#">{{ page }}</a></li>
|
||||
<li class="active"><a class="pagination-disabled" href="#">{{ page }}</a></li>
|
||||
{% else %}
|
||||
<li><a href="?{{ page.querystring }}">{{ page }}</a></li>
|
||||
{% endifequal %}
|
||||
|
||||
@@ -7,6 +7,11 @@ from django.utils.translation import ugettext_lazy as _
|
||||
register = Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def appearance_icon_render(icon_class, enable_shadow=False):
|
||||
return icon_class.render(extra_context={'enable_shadow': enable_shadow})
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_choice_value(field):
|
||||
try:
|
||||
|
||||
19
mayan/apps/authentication/events.py
Normal file
19
mayan/apps/authentication/events.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.events.classes import EventTypeNamespace
|
||||
|
||||
namespace = EventTypeNamespace(
|
||||
label=_('Authentication'), name='authentication'
|
||||
)
|
||||
|
||||
event_user_authentication_error = namespace.add_event_type(
|
||||
label=_('User authentication error'), name='user_authentication_error'
|
||||
)
|
||||
event_user_password_reset_started = namespace.add_event_type(
|
||||
label=_('User password reset started'), name='user_password_reset_started'
|
||||
)
|
||||
event_user_password_reset_complete = namespace.add_event_type(
|
||||
label=_('User password reset complete'), name='user_password_reset_complete'
|
||||
)
|
||||
82
mayan/apps/authentication/tests/test_events.py
Normal file
82
mayan/apps/authentication/tests/test_events.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.views import (
|
||||
INTERNAL_RESET_SESSION_TOKEN, INTERNAL_RESET_URL_TOKEN,
|
||||
)
|
||||
from django.core import mail
|
||||
|
||||
from actstream.models import Action
|
||||
|
||||
from mayan.apps.common.tests.base import GenericViewTestCase
|
||||
from mayan.apps.events.utils import create_system_user
|
||||
|
||||
from ..events import (
|
||||
event_user_authentication_error, event_user_password_reset_complete,
|
||||
event_user_password_reset_started
|
||||
)
|
||||
|
||||
|
||||
class AuthenticationEventsTestCase(GenericViewTestCase):
|
||||
auto_login_user = False
|
||||
|
||||
def setUp(self):
|
||||
super(AuthenticationEventsTestCase, self).setUp()
|
||||
create_system_user()
|
||||
|
||||
def test_user_authentication_failure_event(self):
|
||||
Action.objects.all().delete()
|
||||
response = self.post(viewname=settings.LOGIN_URL)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
action = Action.objects.last()
|
||||
self.assertEqual(action.verb, event_user_authentication_error.id)
|
||||
|
||||
def test_user_password_reset_started_event(self):
|
||||
Action.objects.all().delete()
|
||||
response = self.post(
|
||||
viewname='authentication:password_reset_view', data={
|
||||
'email': self._test_case_user.email,
|
||||
}
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
||||
action = Action.objects.last()
|
||||
self.assertEqual(action.verb, event_user_password_reset_started.id)
|
||||
|
||||
def test_user_password_reset_complete_event(self):
|
||||
response = self.post(
|
||||
viewname='authentication:password_reset_view', data={
|
||||
'email': self._test_case_user.email,
|
||||
}
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
||||
email_parts = mail.outbox[0].body.replace('\n', '').split('/')
|
||||
uidb64 = email_parts[-3]
|
||||
token = email_parts[-2]
|
||||
|
||||
# Add the token to the session
|
||||
session = self.client.session
|
||||
session[INTERNAL_RESET_SESSION_TOKEN] = token
|
||||
session.save()
|
||||
|
||||
Action.objects.all().delete()
|
||||
|
||||
new_password = 'new_password_123'
|
||||
response = self.post(
|
||||
viewname='authentication:password_reset_confirm_view',
|
||||
kwargs={'uidb64': uidb64, 'token': INTERNAL_RESET_URL_TOKEN}, data={
|
||||
'new_password1': new_password,
|
||||
'new_password2': new_password
|
||||
}
|
||||
)
|
||||
|
||||
self.assertNotIn(INTERNAL_RESET_SESSION_TOKEN, self.client.session)
|
||||
|
||||
action = Action.objects.last()
|
||||
self.assertEqual(action.verb, event_user_password_reset_complete.id)
|
||||
@@ -11,7 +11,7 @@ from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils.http import urlunquote_plus
|
||||
|
||||
from mayan.apps.common.tests import GenericViewTestCase
|
||||
from mayan.apps.common.tests.base import GenericViewTestCase
|
||||
from mayan.apps.smart_settings.classes import Namespace
|
||||
from mayan.apps.user_management.permissions import permission_user_edit
|
||||
from mayan.apps.user_management.tests.literals import TEST_USER_PASSWORD_EDITED
|
||||
|
||||
@@ -21,8 +21,13 @@ from mayan.apps.common.generics import MultipleObjectFormActionView
|
||||
from mayan.apps.common.settings import (
|
||||
setting_home_view, setting_project_title, setting_project_url
|
||||
)
|
||||
from mayan.apps.events.utils import get_system_user
|
||||
from mayan.apps.user_management.permissions import permission_user_edit
|
||||
|
||||
from .events import (
|
||||
event_user_authentication_error, event_user_password_reset_complete,
|
||||
event_user_password_reset_started
|
||||
)
|
||||
from .forms import EmailAuthenticationForm, UsernameAuthenticationForm
|
||||
from .settings import setting_login_method, setting_maximum_session_length
|
||||
|
||||
@@ -57,6 +62,10 @@ class MayanLoginView(StrongholdPublicMixin, LoginView):
|
||||
|
||||
return result
|
||||
|
||||
def form_invalid(self, form):
|
||||
event_user_authentication_error.commit(actor=get_system_user())
|
||||
return super(MayanLoginView, self).form_invalid(form=form)
|
||||
|
||||
def get_form_class(self):
|
||||
if setting_login_method.value == 'email':
|
||||
return EmailAuthenticationForm
|
||||
@@ -112,6 +121,10 @@ class MayanPasswordResetConfirmView(StrongholdPublicMixin, PasswordResetConfirmV
|
||||
)
|
||||
template_name = 'authentication/password_reset_confirm.html'
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
event_user_password_reset_complete.commit(actor=get_system_user())
|
||||
return super(MayanPasswordResetConfirmView, self).post(*args, **kwargs)
|
||||
|
||||
|
||||
class MayanPasswordResetDoneView(StrongholdPublicMixin, PasswordResetDoneView):
|
||||
extra_context = {
|
||||
@@ -137,6 +150,10 @@ class MayanPasswordResetView(StrongholdPublicMixin, PasswordResetView):
|
||||
)
|
||||
template_name = 'authentication/password_reset_form.html'
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
event_user_password_reset_started.commit(actor=get_system_user())
|
||||
return super(MayanPasswordResetView, self).post(*args, **kwargs)
|
||||
|
||||
|
||||
class UserSetPasswordView(MultipleObjectFormActionView):
|
||||
form_class = SetPasswordForm
|
||||
|
||||
@@ -4,21 +4,23 @@
|
||||
|
||||
{% if autoadmin_properties.account %}
|
||||
<div class="row">
|
||||
<div class="col-xs-10 col-xs-offset-1 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4">
|
||||
<div class="col-xs-12 col-sm-10 col-sm-offset-1 col-md-10 col-md-offset-1 col-lg-6 col-lg-offset-3">
|
||||
<br>
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans 'First time login' %}</h3>
|
||||
<h3 class="text-center panel-title">{% trans 'Automatic credentials' %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="content login">
|
||||
{% smart_setting 'COMMON_PROJECT_TITLE' as project_title %}
|
||||
<p>{% blocktrans %}You have just finished installing <strong>{{ project_title }}</strong>, congratulations!{% endblocktrans %}</p>
|
||||
<p>{% trans 'Login using the following credentials:' %}</p>
|
||||
<p>{% blocktrans with autoadmin_properties.account as account %}Username: <strong>{{ account }}</strong>{% endblocktrans %}</p>
|
||||
<p>{% blocktrans with autoadmin_properties.account.email as email %}Email: <strong>{{ email }}</strong>{% endblocktrans %}</p>
|
||||
<p>{% blocktrans with autoadmin_properties.password as password %}Password: <strong>{{ password }}</strong>{% endblocktrans %}</p>
|
||||
<p>{% trans 'Be sure to change the password to increase security and to disable this message.' %}</p>
|
||||
<p class="text-center">{% blocktrans %}You have just finished installing <strong>{{ project_title }}</strong>, congratulations!{% endblocktrans %}</p>
|
||||
<p class="text-center">{% trans 'Login using the following credentials:' %}</p>
|
||||
<p class="text-center">
|
||||
{% blocktrans with autoadmin_properties.account as account %}Username: <strong>{{ account }}</strong>{% endblocktrans %}<br>
|
||||
{% blocktrans with autoadmin_properties.account.email as email %}Email: <strong>{{ email }}</strong>{% endblocktrans %}<br>
|
||||
{% blocktrans with autoadmin_properties.password as password %}Password: <strong>{{ password }}</strong>{% endblocktrans %}
|
||||
</p>
|
||||
<p class="text-center">{% trans 'Be sure to change the password to increase security and to disable this message.' %}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,5 +3,5 @@ from __future__ import unicode_literals
|
||||
TEST_ADMIN_USER_EMAIL = 'testemail@example.com'
|
||||
TEST_ADMIN_USER_PASSWORD = 'test admin user password'
|
||||
TEST_ADMIN_USER_USERNAME = 'test_admin_user_username'
|
||||
TEST_FIRST_TIME_LOGIN_TEXT = 'First time login'
|
||||
TEST_FIRST_TIME_LOGIN_TEXT = 'Automatic credentials'
|
||||
TEST_MOCK_VIEW_TEXT = 'mock view text'
|
||||
|
||||
@@ -2,20 +2,24 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core import management
|
||||
from django.test import TestCase
|
||||
|
||||
from mayan.apps.common.tests.base import BaseTestCase
|
||||
from mayan.apps.common.tests.utils import mute_stdout
|
||||
|
||||
from ..models import AutoAdminSingleton
|
||||
|
||||
|
||||
class AutoAdminManagementCommandTestCase(TestCase):
|
||||
class AutoAdminManagementCommandTestCase(BaseTestCase):
|
||||
create_test_case_user = False
|
||||
|
||||
def setUp(self):
|
||||
super(AutoAdminManagementCommandTestCase, self).setUp()
|
||||
with mute_stdout():
|
||||
management.call_command('createautoadmin')
|
||||
|
||||
def tearDown(self):
|
||||
AutoAdminSingleton.objects.all().delete()
|
||||
super(AutoAdminManagementCommandTestCase, self).tearDown()
|
||||
|
||||
def test_autoadmin_creation(self):
|
||||
autoadmin = AutoAdminSingleton.objects.get()
|
||||
|
||||
@@ -2,8 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from mayan.apps.common.tests.base import BaseTestCase
|
||||
from mayan.apps.common.tests.utils import mute_stdout
|
||||
|
||||
from ..models import AutoAdminSingleton
|
||||
@@ -12,7 +11,7 @@ from ..settings import setting_username
|
||||
from .literals import TEST_ADMIN_USER_PASSWORD
|
||||
|
||||
|
||||
class AutoAdminHandlerTestCase(TestCase):
|
||||
class AutoAdminHandlerTestCase(BaseTestCase):
|
||||
def test_post_admin_creation(self):
|
||||
logging.disable(logging.INFO)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.common.settings import setting_home_view
|
||||
from mayan.apps.common.tests import GenericViewTestCase
|
||||
from mayan.apps.common.tests.base import GenericViewTestCase
|
||||
from mayan.apps.common.tests.utils import mute_stdout
|
||||
|
||||
from ..models import AutoAdminSingleton
|
||||
|
||||
20
mayan/apps/cabinets/migrations/0002_auto_20190729_0236.py
Normal file
20
mayan/apps/cabinets/migrations/0002_auto_20190729_0236.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.22 on 2019-07-29 02:36
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cabinets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='cabinet',
|
||||
name='label',
|
||||
field=models.CharField(help_text='A short text used to identify the cabinet.', max_length=128, verbose_name='Label'),
|
||||
),
|
||||
]
|
||||
@@ -32,7 +32,10 @@ class Cabinet(MPTTModel):
|
||||
blank=True, db_index=True, null=True, on_delete=models.CASCADE,
|
||||
related_name='children', to='self'
|
||||
)
|
||||
label = models.CharField(max_length=128, verbose_name=_('Label'))
|
||||
label = models.CharField(
|
||||
help_text=_('A short text used to identify the cabinet.'),
|
||||
max_length=128, verbose_name=_('Label')
|
||||
)
|
||||
documents = models.ManyToManyField(
|
||||
blank=True, related_name='cabinets', to=Document,
|
||||
verbose_name=_('Documents')
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-2">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-2" style="margin-right: -25px;">{# Remove gutter #}
|
||||
<h4>{% trans 'Navigation:' %}</h4>
|
||||
<div id="jstree"></div>
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,8 @@ from django.utils.encoding import force_text
|
||||
from rest_framework import status
|
||||
|
||||
from mayan.apps.documents.permissions import permission_document_view
|
||||
from mayan.apps.documents.tests import DocumentTestMixin
|
||||
from mayan.apps.rest_api.tests import BaseAPITestCase
|
||||
from mayan.apps.documents.tests.mixins import DocumentTestMixin
|
||||
from mayan.apps.rest_api.tests.base import BaseAPITestCase
|
||||
|
||||
from ..models import Cabinet
|
||||
from ..permissions import (
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from actstream.models import Action
|
||||
|
||||
from mayan.apps.common.tests import GenericViewTestCase
|
||||
from mayan.apps.common.tests.base import GenericViewTestCase
|
||||
from mayan.apps.documents.tests.test_models import GenericDocumentTestCase
|
||||
|
||||
from ..events import (
|
||||
|
||||
@@ -2,8 +2,8 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from mayan.apps.common.tests import BaseTestCase
|
||||
from mayan.apps.documents.tests import DocumentTestMixin
|
||||
from mayan.apps.common.tests.base import BaseTestCase
|
||||
from mayan.apps.documents.tests.mixins import DocumentTestMixin
|
||||
|
||||
from ..models import Cabinet
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from mayan.apps.common.tests import GenericViewTestCase
|
||||
from mayan.apps.common.tests.base import GenericViewTestCase
|
||||
from mayan.apps.documents.permissions import permission_document_view
|
||||
from mayan.apps.documents.tests import GenericDocumentViewTestCase
|
||||
from mayan.apps.documents.tests.base import GenericDocumentViewTestCase
|
||||
|
||||
from ..models import Cabinet
|
||||
from ..permissions import (
|
||||
|
||||
@@ -2,9 +2,8 @@ from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.documents.models import Document
|
||||
from mayan.apps.documents.permissions import permission_document_create
|
||||
from mayan.apps.documents.tests import (
|
||||
GenericDocumentViewTestCase, TEST_SMALL_DOCUMENT_PATH,
|
||||
)
|
||||
from mayan.apps.documents.tests.base import GenericDocumentViewTestCase
|
||||
from mayan.apps.documents.tests.literals import TEST_SMALL_DOCUMENT_PATH
|
||||
from mayan.apps.sources.models import WebFormSource
|
||||
from mayan.apps.sources.tests.literals import (
|
||||
TEST_SOURCE_LABEL, TEST_SOURCE_UNCOMPRESS_N
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.common.tests import GenericViewTestCase
|
||||
from mayan.apps.common.tests.base import GenericViewTestCase
|
||||
from mayan.apps.document_states.tests.mixins import WorkflowTestMixin
|
||||
from mayan.apps.document_states.tests.test_workflow_actions import ActionTestCase
|
||||
|
||||
@@ -46,7 +46,7 @@ class CabinetWorkflowActionViewTestCase(
|
||||
self._create_test_workflow_state()
|
||||
|
||||
response = self.get(
|
||||
viewname='document_states:setup_workflow_state_action_create',
|
||||
viewname='document_states:workflow_template_state_action_create',
|
||||
kwargs={
|
||||
'pk': self.test_workflow_state.pk,
|
||||
'class_path': 'mayan.apps.cabinets.workflow_actions.CabinetAddAction'
|
||||
@@ -61,7 +61,7 @@ class CabinetWorkflowActionViewTestCase(
|
||||
self._create_test_cabinet()
|
||||
|
||||
response = self.get(
|
||||
viewname='document_states:setup_workflow_state_action_create',
|
||||
viewname='document_states:workflow_template_state_action_create',
|
||||
kwargs={
|
||||
'pk': self.test_workflow_state.pk,
|
||||
'class_path': 'mayan.apps.cabinets.workflow_actions.CabinetRemoveAction'
|
||||
|
||||
@@ -12,55 +12,62 @@ from .views import (
|
||||
CabinetDeleteView, CabinetDetailView, CabinetEditView, CabinetListView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
urlpatterns_cabinets = [
|
||||
url(
|
||||
regex=r'^list/$', view=CabinetListView.as_view(), name='cabinet_list'
|
||||
regex=r'^cabinets/$', view=CabinetListView.as_view(), name='cabinet_list'
|
||||
),
|
||||
url(
|
||||
regex=r'^(?P<pk>\d+)/child/add/$', view=CabinetChildAddView.as_view(),
|
||||
name='cabinet_child_add'
|
||||
),
|
||||
url(
|
||||
regex=r'^create/$', view=CabinetCreateView.as_view(),
|
||||
regex=r'^cabinets/create/$', view=CabinetCreateView.as_view(),
|
||||
name='cabinet_create'
|
||||
),
|
||||
url(
|
||||
regex=r'^(?P<pk>\d+)/edit/$', view=CabinetEditView.as_view(),
|
||||
name='cabinet_edit'
|
||||
regex=r'^cabinets/(?P<pk>\d+)/children/add/$', view=CabinetChildAddView.as_view(),
|
||||
name='cabinet_child_add'
|
||||
),
|
||||
url(
|
||||
regex=r'^(?P<pk>\d+)/delete/$', view=CabinetDeleteView.as_view(),
|
||||
regex=r'^cabinets/(?P<pk>\d+)/delete/$', view=CabinetDeleteView.as_view(),
|
||||
name='cabinet_delete'
|
||||
),
|
||||
url(
|
||||
regex=r'^(?P<pk>\d+)/$', view=CabinetDetailView.as_view(),
|
||||
name='cabinet_view'
|
||||
regex=r'^cabinets/(?P<pk>\d+)/edit/$', view=CabinetEditView.as_view(),
|
||||
name='cabinet_edit'
|
||||
),
|
||||
url(
|
||||
regex=r'^document/(?P<pk>\d+)/cabinet/add/$',
|
||||
regex=r'^cabinets/(?P<pk>\d+)/$', view=CabinetDetailView.as_view(),
|
||||
name='cabinet_view'
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns_documents_cabinets = [
|
||||
url(
|
||||
regex=r'^documents/(?P<pk>\d+)/cabinets/add/$',
|
||||
view=DocumentAddToCabinetView.as_view(), name='document_cabinet_add'
|
||||
),
|
||||
url(
|
||||
regex=r'^document/multiple/cabinet/add/$',
|
||||
regex=r'^documents/multiple/cabinets/add/$',
|
||||
view=DocumentAddToCabinetView.as_view(),
|
||||
name='document_multiple_cabinet_add'
|
||||
),
|
||||
url(
|
||||
regex=r'^document/(?P<pk>\d+)/cabinet/remove/$',
|
||||
regex=r'^documents/(?P<pk>\d+)/cabinets/remove/$',
|
||||
view=DocumentRemoveFromCabinetView.as_view(),
|
||||
name='document_cabinet_remove'
|
||||
),
|
||||
url(
|
||||
regex=r'^document/multiple/cabinet/remove/$',
|
||||
regex=r'^documents/multiple/cabinets/remove/$',
|
||||
view=DocumentRemoveFromCabinetView.as_view(),
|
||||
name='multiple_document_cabinet_remove'
|
||||
),
|
||||
url(
|
||||
regex=r'^document/(?P<pk>\d+)/cabinet/list/$',
|
||||
regex=r'^documents/(?P<pk>\d+)/cabinets/$',
|
||||
view=DocumentCabinetListView.as_view(), name='document_cabinet_list'
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns = []
|
||||
urlpatterns.extend(urlpatterns_cabinets)
|
||||
urlpatterns.extend(urlpatterns_documents_cabinets)
|
||||
|
||||
api_urls = [
|
||||
url(
|
||||
regex=r'^cabinets/(?P<pk>[0-9]+)/documents/(?P<document_pk>[0-9]+)/$',
|
||||
|
||||
@@ -6,9 +6,12 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.acls.classes import ModelPermission
|
||||
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.events.classes import ModelEventType
|
||||
from mayan.apps.navigation.classes import SourceColumn
|
||||
|
||||
from .dashboard_widgets import DashboardWidgetTotalCheckouts
|
||||
from .events import (
|
||||
@@ -17,8 +20,9 @@ from .events import (
|
||||
)
|
||||
from .handlers import handler_check_new_version_creation
|
||||
from .links import (
|
||||
link_check_in_document, link_check_out_document, link_check_out_info,
|
||||
link_check_out_list
|
||||
link_check_in_document, link_check_in_document_multiple,
|
||||
link_check_out_document, link_check_out_document_multiple,
|
||||
link_check_out_info, link_check_out_list
|
||||
)
|
||||
from .methods import (
|
||||
method_check_in, method_get_check_out_info, method_get_check_out_state,
|
||||
@@ -43,6 +47,8 @@ class CheckoutsApp(MayanAppConfig):
|
||||
def ready(self):
|
||||
super(CheckoutsApp, self).ready()
|
||||
|
||||
CheckedOutDocument = self.get_model(model_name='CheckedOutDocument')
|
||||
DocumentCheckout = self.get_model(model_name='DocumentCheckout')
|
||||
Document = apps.get_model(
|
||||
app_label='documents', model_name='Document'
|
||||
)
|
||||
@@ -76,6 +82,22 @@ class CheckoutsApp(MayanAppConfig):
|
||||
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(
|
||||
widget=DashboardWidgetTotalCheckouts, order=-1
|
||||
@@ -85,6 +107,22 @@ class CheckoutsApp(MayanAppConfig):
|
||||
links=(link_check_out_info,), sources=(Document,)
|
||||
)
|
||||
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(
|
||||
links=(link_check_out_document, link_check_in_document),
|
||||
sources=(
|
||||
|
||||
@@ -10,7 +10,7 @@ from .models import DocumentCheckout
|
||||
from .widgets import SplitTimeDeltaWidget
|
||||
|
||||
|
||||
class DocumentCheckoutForm(forms.ModelForm):
|
||||
class DocumentCheckOutForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ('expiration_datetime', 'block_new_version')
|
||||
model = DocumentCheckout
|
||||
@@ -19,7 +19,7 @@ class DocumentCheckoutForm(forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class DocumentCheckoutDefailForm(DetailForm):
|
||||
class DocumentCheckOutDetailForm(DetailForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
instance = kwargs['instance']
|
||||
|
||||
@@ -56,7 +56,7 @@ class DocumentCheckoutDefailForm(DetailForm):
|
||||
)
|
||||
|
||||
kwargs['extra_fields'] = extra_fields
|
||||
super(DocumentCheckoutDefailForm, self).__init__(*args, **kwargs)
|
||||
super(DocumentCheckOutDetailForm, self).__init__(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
fields = ()
|
||||
|
||||
@@ -38,16 +38,26 @@ link_check_out_document = Link(
|
||||
args='object.pk', condition=is_not_checked_out,
|
||||
icon_class=icon_check_out_document,
|
||||
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(
|
||||
args='object.pk', icon_class=icon_check_in_document,
|
||||
condition=is_checked_out, permissions=(
|
||||
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(
|
||||
args='resolved_object.pk', icon_class=icon_check_out_info, permissions=(
|
||||
permission_document_check_out_detail_view,
|
||||
), text=_('Check in/out'), view='checkouts:check_out_info',
|
||||
), text=_('Check in/out'), view='checkouts:check_out_info'
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.apps import apps
|
||||
from django.db import models, transaction
|
||||
from django.utils.timezone import now
|
||||
|
||||
from mayan.apps.acls.models import AccessControlList
|
||||
from mayan.apps.documents.models import Document
|
||||
|
||||
from .events import (
|
||||
@@ -14,10 +15,58 @@ from .events import (
|
||||
)
|
||||
from .exceptions import DocumentNotCheckedOut
|
||||
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__)
|
||||
|
||||
|
||||
class DocumentCheckoutBusinessLogicManager(models.Manager):
|
||||
def check_in_document(self, document, user=None):
|
||||
# Convert any document submodel to the parent model class
|
||||
queryset = document._meta.default_manager.filter(pk=document.pk)
|
||||
|
||||
if not self.filter(document__pk__in=queryset).exists():
|
||||
raise DocumentNotCheckedOut
|
||||
|
||||
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):
|
||||
def are_document_new_versions_allowed(self, document, user=None):
|
||||
try:
|
||||
@@ -27,25 +76,6 @@ class DocumentCheckoutManager(models.Manager):
|
||||
else:
|
||||
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):
|
||||
for document in self.expired_check_outs():
|
||||
document.check_in()
|
||||
@@ -57,7 +87,11 @@ class DocumentCheckoutManager(models.Manager):
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
|
||||
@@ -74,7 +108,11 @@ class DocumentCheckoutManager(models.Manager):
|
||||
return STATE_CHECKED_IN
|
||||
|
||||
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(
|
||||
expiration_datetime__lte=now()
|
||||
).values_list('document__pk', flat=True)
|
||||
@@ -83,9 +121,6 @@ class DocumentCheckoutManager(models.Manager):
|
||||
return expired_list
|
||||
|
||||
def get_by_natural_key(self, document_natural_key):
|
||||
Document = apps.get_model(
|
||||
app_label='documents', model_name='Document'
|
||||
)
|
||||
try:
|
||||
document = Document.objects.get_by_natural_key(document_natural_key)
|
||||
except Document.DoesNotExist:
|
||||
|
||||
@@ -8,7 +8,7 @@ def method_check_in(self, user=None):
|
||||
app_label='checkouts', model_name='DocumentCheckout'
|
||||
)
|
||||
|
||||
return DocumentCheckout.objects.check_in_document(
|
||||
return DocumentCheckout.business_logic.check_in_document(
|
||||
document=self, user=user
|
||||
)
|
||||
|
||||
|
||||
26
mayan/apps/checkouts/migrations/0008_checkedoutdocument.py
Normal file
26
mayan/apps/checkouts/migrations/0008_checkedoutdocument.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.20 on 2019-07-25 04:52
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('documents', '0050_auto_20190725_0451'),
|
||||
('checkouts', '0007_auto_20180310_1715'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CheckedOutDocument',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
},
|
||||
bases=('documents.document',),
|
||||
),
|
||||
]
|
||||
@@ -14,7 +14,10 @@ from mayan.apps.documents.models import Document
|
||||
|
||||
from .events import event_document_check_out
|
||||
from .exceptions import DocumentAlreadyCheckedOut
|
||||
from .managers import DocumentCheckoutManager, NewVersionBlockManager
|
||||
from .managers import (
|
||||
DocumentCheckoutBusinessLogicManager, DocumentCheckoutManager,
|
||||
NewVersionBlockManager
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,6 +52,7 @@ class DocumentCheckout(models.Model):
|
||||
)
|
||||
|
||||
objects = DocumentCheckoutManager()
|
||||
business_logic = DocumentCheckoutBusinessLogicManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
@@ -81,13 +85,13 @@ class DocumentCheckout(models.Model):
|
||||
natural_key.dependencies = ['documents.Document']
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
new_checkout = not self.pk
|
||||
if not new_checkout or self.document.is_checked_out():
|
||||
is_new = not self.pk
|
||||
if not is_new or self.document.is_checked_out():
|
||||
raise DocumentAlreadyCheckedOut
|
||||
|
||||
with transaction.atomic():
|
||||
result = super(DocumentCheckout, self).save(*args, **kwargs)
|
||||
if new_checkout:
|
||||
if is_new:
|
||||
event_document_check_out.commit(
|
||||
actor=self.user, target=self.document
|
||||
)
|
||||
@@ -119,3 +123,24 @@ class NewVersionBlock(models.Model):
|
||||
def natural_key(self):
|
||||
return self.document.natural_key()
|
||||
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')
|
||||
|
||||
@@ -5,6 +5,7 @@ import datetime
|
||||
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
|
||||
|
||||
@@ -64,16 +65,13 @@ class DocumentCheckoutViewTestMixin(object):
|
||||
}
|
||||
)
|
||||
|
||||
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_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_list_view(self):
|
||||
return self.get(viewname='checkouts:check_out_list')
|
||||
|
||||
def _request_test_document_check_out_view(self):
|
||||
return self.post(
|
||||
viewname='checkouts:check_out_document', kwargs={
|
||||
@@ -84,3 +82,23 @@ class DocumentCheckoutViewTestMixin(object):
|
||||
'block_new_version': True
|
||||
}
|
||||
)
|
||||
|
||||
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_unit': TIME_DELTA_UNIT_DAYS,
|
||||
'expiration_datetime_amount': 99,
|
||||
'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')
|
||||
|
||||
@@ -4,9 +4,9 @@ from django.utils.encoding import force_text
|
||||
|
||||
from rest_framework import status
|
||||
|
||||
from mayan.apps.documents.tests import DocumentTestMixin
|
||||
from mayan.apps.documents.permissions import permission_document_view
|
||||
from mayan.apps.rest_api.tests import BaseAPITestCase
|
||||
from mayan.apps.documents.tests.mixins import DocumentTestMixin
|
||||
from mayan.apps.rest_api.tests.base import BaseAPITestCase
|
||||
|
||||
from ..models import DocumentCheckout
|
||||
from ..permissions import (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.documents.tests import GenericDocumentViewTestCase
|
||||
from mayan.apps.documents.tests.base import GenericDocumentViewTestCase
|
||||
|
||||
from ..links import link_check_out_document, link_check_out_info
|
||||
from ..permissions import (
|
||||
|
||||
@@ -2,11 +2,10 @@ from __future__ import unicode_literals
|
||||
|
||||
import time
|
||||
|
||||
from mayan.apps.common.tests import BaseTestCase
|
||||
from mayan.apps.documents.tests import (
|
||||
GenericDocumentTestCase, DocumentTestMixin
|
||||
)
|
||||
from mayan.apps.common.tests.base import BaseTestCase
|
||||
from mayan.apps.documents.tests.base import GenericDocumentTestCase
|
||||
from mayan.apps.documents.tests.literals import TEST_SMALL_DOCUMENT_PATH
|
||||
from mayan.apps.documents.tests.mixins import DocumentTestMixin
|
||||
|
||||
from ..exceptions import (
|
||||
DocumentAlreadyCheckedOut, DocumentNotCheckedOut,
|
||||
@@ -53,7 +52,7 @@ class DocumentCheckoutTestCase(
|
||||
block_new_version=True
|
||||
)
|
||||
|
||||
def test_document_checkin_without_checkout(self):
|
||||
def test_document_check_in_without_check_out(self):
|
||||
with self.assertRaises(DocumentNotCheckedOut):
|
||||
self.test_document.check_in()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.documents.permissions import permission_document_view
|
||||
from mayan.apps.documents.tests import GenericDocumentViewTestCase
|
||||
from mayan.apps.documents.tests.base import GenericDocumentViewTestCase
|
||||
from mayan.apps.sources.links import link_document_version_upload
|
||||
|
||||
from ..literals import STATE_CHECKED_OUT, STATE_LABELS
|
||||
@@ -22,8 +22,8 @@ class DocumentCheckoutViewTestCase(
|
||||
self._check_out_test_document()
|
||||
|
||||
response = self._request_test_document_check_in_get_view()
|
||||
self.assertContains(
|
||||
response=response, text=self.test_document.label, status_code=200
|
||||
self.assertNotContains(
|
||||
response=response, text=self.test_document.label, status_code=404
|
||||
)
|
||||
|
||||
self.assertTrue(self.test_document.is_checked_out())
|
||||
@@ -46,7 +46,7 @@ class DocumentCheckoutViewTestCase(
|
||||
self._check_out_test_document()
|
||||
|
||||
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())
|
||||
|
||||
@@ -67,9 +67,89 @@ class DocumentCheckoutViewTestCase(
|
||||
)
|
||||
)
|
||||
|
||||
def test_document_multiple_check_in_post_view_no_permission(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])
|
||||
|
||||
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_document_multiple_check_in_post_view_with_document_0_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
|
||||
)
|
||||
|
||||
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, 403)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.assertFalse(self.test_document.is_checked_out())
|
||||
|
||||
@@ -87,6 +167,102 @@ class DocumentCheckoutViewTestCase(
|
||||
|
||||
self.assertTrue(self.test_document.is_checked_out())
|
||||
|
||||
def test_document_multiple_check_out_post_view_no_permission(self):
|
||||
# Upload second document
|
||||
self.upload_document()
|
||||
|
||||
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, 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_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()
|
||||
|
||||
@@ -156,19 +332,18 @@ class DocumentCheckoutViewTestCase(
|
||||
'pk': self.test_document.pk
|
||||
}
|
||||
)
|
||||
self.assertContains(
|
||||
response=response, text='Insufficient permissions', status_code=403
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.assertTrue(self.test_document.is_checked_out())
|
||||
|
||||
def test_document_check_in_forcefull_view_with_permission(self):
|
||||
def test_document_check_in_forcefull_view_with_access(self):
|
||||
self._create_test_user()
|
||||
# Check out document as test_user
|
||||
self._check_out_test_document(user=self.test_user)
|
||||
|
||||
self.grant_access(
|
||||
obj=self.test_document, permission=permission_document_check_in_override
|
||||
obj=self.test_document,
|
||||
permission=permission_document_check_in_override
|
||||
)
|
||||
|
||||
# Check in document as test_case_user
|
||||
@@ -219,6 +394,8 @@ class NewVersionBlockViewTestCase(
|
||||
|
||||
# Needed by the url view resolver
|
||||
response.context.current_app = None
|
||||
resolved_link = link_document_version_upload.resolve(context=response.context)
|
||||
resolved_link = link_document_version_upload.resolve(
|
||||
context=response.context
|
||||
)
|
||||
|
||||
self.assertEqual(resolved_link, None)
|
||||
|
||||
@@ -4,8 +4,8 @@ from django.conf.urls import url
|
||||
|
||||
from .api_views import APICheckedoutDocumentListView, APICheckedoutDocumentView
|
||||
from .views import (
|
||||
DocumentCheckOutView, DocumentCheckOutDetailView, DocumentCheckOutListView,
|
||||
DocumentCheckInView
|
||||
DocumentCheckInView, DocumentCheckOutDetailView, DocumentCheckOutListView,
|
||||
DocumentCheckOutView
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
@@ -14,16 +14,25 @@ urlpatterns = [
|
||||
name='check_out_list'
|
||||
),
|
||||
url(
|
||||
regex=r'^documents/(?P<pk>\d+)/check/out/$', view=DocumentCheckOutView.as_view(),
|
||||
name='check_out_document'
|
||||
regex=r'^documents/(?P<pk>\d+)/check/in/$',
|
||||
view=DocumentCheckInView.as_view(), name='check_in_document'
|
||||
),
|
||||
url(
|
||||
regex=r'^documents/(?P<pk>\d+)/check/in/$', view=DocumentCheckInView.as_view(),
|
||||
name='check_in_document'
|
||||
regex=r'^documents/multiple/check/in/$',
|
||||
name='check_in_document_multiple', view=DocumentCheckInView.as_view()
|
||||
),
|
||||
url(
|
||||
regex=r'^documents/(?P<pk>\d+)/check/info/$', view=DocumentCheckOutDetailView.as_view(),
|
||||
name='check_out_info'
|
||||
regex=r'^documents/(?P<pk>\d+)/check/out/$',
|
||||
view=DocumentCheckOutView.as_view(), name='check_out_document'
|
||||
),
|
||||
url(
|
||||
regex=r'^documents/multiple/check/out/$',
|
||||
name='check_out_document_multiple',
|
||||
view=DocumentCheckOutView.as_view()
|
||||
),
|
||||
url(
|
||||
regex=r'^documents/(?P<pk>\d+)/checkout/info/$',
|
||||
view=DocumentCheckOutDetailView.as_view(), name='check_out_info'
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
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.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.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.views import DocumentListView
|
||||
|
||||
from .exceptions import DocumentAlreadyCheckedOut, DocumentNotCheckedOut
|
||||
from .forms import DocumentCheckoutForm, DocumentCheckoutDefailForm
|
||||
from .forms import DocumentCheckOutForm, DocumentCheckOutDetailForm
|
||||
from .icons import icon_check_out_info
|
||||
from .models import DocumentCheckout
|
||||
from .permissions import (
|
||||
@@ -24,66 +20,125 @@ from .permissions import (
|
||||
)
|
||||
|
||||
|
||||
class DocumentCheckInView(ConfirmView):
|
||||
class DocumentCheckInView(MultipleObjectConfirmActionView):
|
||||
error_message = 'Unable to check in document "%(instance)s". %(exception)s'
|
||||
model = Document
|
||||
pk_url_kwarg = 'pk'
|
||||
success_message_singular = '%(count)d document checked in.'
|
||||
success_message_plural = '%(count)d documents checked in.'
|
||||
|
||||
def get_extra_context(self):
|
||||
document = self.get_object()
|
||||
queryset = self.get_object_list()
|
||||
|
||||
context = {
|
||||
'object': document,
|
||||
result = {
|
||||
'title': ungettext(
|
||||
singular='Check in %(count)d document',
|
||||
plural='Check in %(count)d documents',
|
||||
number=queryset.count()
|
||||
) % {
|
||||
'count': queryset.count(),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if queryset.count() == 1:
|
||||
result.update(
|
||||
{
|
||||
'object': queryset.first(),
|
||||
'title': _(
|
||||
'Check in document: %s'
|
||||
) % queryset.first()
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
return result
|
||||
|
||||
def get_object(self):
|
||||
return get_object_or_404(klass=Document, pk=self.kwargs['pk'])
|
||||
|
||||
def get_post_action_redirect(self):
|
||||
def get_post_object_action_url(self):
|
||||
if self.action_count == 1:
|
||||
return reverse(
|
||||
viewname='checkouts:check_out_info', kwargs={
|
||||
'pk': self.get_object().pk
|
||||
viewname='checkouts:document_checkout_info',
|
||||
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
|
||||
)
|
||||
|
||||
check_in_override_queryset = AccessControlList.objects.restrict_queryset(
|
||||
permission=permission_document_check_in_override,
|
||||
queryset=source_queryset, user=self.request.user
|
||||
)
|
||||
|
||||
return check_in_queryset | check_in_override_queryset
|
||||
|
||||
def object_action(self, form, instance):
|
||||
DocumentCheckout.business_logic.check_in_document(
|
||||
document=instance, user=self.request.user
|
||||
)
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
)
|
||||
|
||||
def view_action(self):
|
||||
document = self.get_object()
|
||||
return result
|
||||
|
||||
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
|
||||
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:
|
||||
AccessControlList.objects.check_access(
|
||||
obj=document,
|
||||
permissions=(permission_document_check_in_override,),
|
||||
user=self.request.user
|
||||
)
|
||||
super(DocumentCheckOutView, self).get_post_action_redirect()
|
||||
|
||||
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
|
||||
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 = DocumentCheckOutDetailForm
|
||||
model = Document
|
||||
object_permission = permission_document_check_out_detail_view
|
||||
|
||||
@@ -96,55 +151,6 @@ class DocumentCheckOutDetailView(SingleObjectDetailView):
|
||||
}
|
||||
|
||||
|
||||
class DocumentCheckOutView(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(
|
||||
DocumentCheckOutView, 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):
|
||||
return {
|
||||
'object': self.document,
|
||||
'title': _('Check out document: %s') % self.document
|
||||
}
|
||||
|
||||
def get_post_action_redirect(self):
|
||||
return reverse(
|
||||
viewname='checkouts:check_out_info', kwargs={
|
||||
'pk': self.document.pk
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class DocumentCheckOutListView(DocumentListView):
|
||||
def get_document_queryset(self):
|
||||
return AccessControlList.objects.restrict_queryset(
|
||||
@@ -157,34 +163,13 @@ class DocumentCheckOutListView(DocumentListView):
|
||||
context = super(DocumentCheckOutListView, self).get_extra_context()
|
||||
context.update(
|
||||
{
|
||||
'extra_columns': (
|
||||
{
|
||||
'name': _('User'),
|
||||
'attribute': encapsulate(
|
||||
lambda document: document.get_check_out_info().user.get_full_name() or document.get_check_out_info().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.'
|
||||
'Checking out a document, blocks certain operations '
|
||||
'for a predetermined amount of time.'
|
||||
),
|
||||
'no_results_title': _('No documents have been checked out'),
|
||||
'title': _('Documents checked out'),
|
||||
'title': _('Checked out documents'),
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
@@ -27,9 +27,7 @@ from .links import (
|
||||
)
|
||||
|
||||
from .literals import MESSAGE_SQLITE_WARNING
|
||||
from .menus import (
|
||||
menu_about, menu_main, menu_secondary, menu_user
|
||||
)
|
||||
from .menus import menu_about, menu_secondary, menu_topbar, menu_user
|
||||
from .settings import (
|
||||
setting_auto_logging, setting_production_error_log_path,
|
||||
setting_production_error_logging
|
||||
@@ -97,7 +95,10 @@ class CommonApp(MayanAppConfig):
|
||||
)
|
||||
|
||||
Template(
|
||||
name='menu_main', template_name='appearance/main_menu.html'
|
||||
name='menu_main', template_name='appearance/menu_main.html'
|
||||
)
|
||||
Template(
|
||||
name='menu_topbar', template_name='appearance/menu_topbar.html'
|
||||
)
|
||||
|
||||
menu_user.bind_links(
|
||||
@@ -112,7 +113,7 @@ class CommonApp(MayanAppConfig):
|
||||
)
|
||||
)
|
||||
|
||||
menu_main.bind_links(links=(menu_about, menu_user,), position=99)
|
||||
menu_topbar.bind_links(links=(menu_about, menu_user,), position=99)
|
||||
menu_secondary.bind_links(
|
||||
links=(link_object_error_list_clear,), sources=(
|
||||
'common:object_error_list',
|
||||
|
||||
@@ -61,102 +61,9 @@ PythonDependency(
|
||||
SOFTWARE.
|
||||
''', module=__name__, name='PyYAML', version_string='==5.1.1'
|
||||
)
|
||||
PythonDependency(
|
||||
copyright_text='''
|
||||
Copyright (c) 2015 Ask Solem & contributors. All rights reserved.
|
||||
Copyright (c) 2012-2014 GoPivotal, Inc. All rights reserved.
|
||||
Copyright (c) 2009, 2010, 2011, 2012 Ask Solem, and individual contributors. All rights reserved.
|
||||
|
||||
Celery is licensed under The BSD License (3 Clause, also known as
|
||||
the new BSD license). The license is an OSI approved Open Source
|
||||
license and is GPL-compatible(1).
|
||||
|
||||
The license text can also be found here:
|
||||
http://www.opensource.org/licenses/BSD-3-Clause
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Ask Solem, nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Ask Solem OR CONTRIBUTORS
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Documentation License
|
||||
=====================
|
||||
|
||||
The documentation portion of Celery (the rendered contents of the
|
||||
"docs" directory of a software distribution or checkout) is supplied
|
||||
under the Creative Commons Attribution-Noncommercial-Share Alike 3.0
|
||||
United States License as described by
|
||||
http://creativecommons.org/licenses/by-nc-sa/3.0/us/
|
||||
|
||||
Footnotes
|
||||
=========
|
||||
(1) A GPL-compatible license makes it possible to
|
||||
combine Celery with other software that is released
|
||||
under the GPL, it does not mean that we're distributing
|
||||
Celery under the GPL license. The BSD license, unlike the GPL,
|
||||
let you distribute a modified version without making your
|
||||
changes open source.
|
||||
''', module=__name__, name='celery', version_string='==3.1.24'
|
||||
)
|
||||
PythonDependency(
|
||||
copyright_text='''
|
||||
Copyright (c) 2012-2013 GoPivotal, Inc. All Rights Reserved.
|
||||
Copyright (c) 2009-2012 Ask Solem. All Rights Reserved.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
Neither the name of Ask Solem nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
''', module=__name__, name='django-celery', version_string='==3.2.1'
|
||||
)
|
||||
PythonDependency(
|
||||
module=__name__, name='django-downloadview', version_string='==1.9'
|
||||
)
|
||||
PythonDependency(
|
||||
module=__name__, name='django-environ', version_string='==0.4.5'
|
||||
)
|
||||
PythonDependency(
|
||||
module=__name__, name='django-formtools', version_string='==2.1'
|
||||
)
|
||||
@@ -383,6 +290,10 @@ PythonDependency(
|
||||
module=__name__, environment=environment_development, name='Werkzeug',
|
||||
version_string='==0.15.4'
|
||||
)
|
||||
PythonDependency(
|
||||
module=__name__, environment=environment_development, name='devpi-server',
|
||||
version_string='==5.0.0'
|
||||
)
|
||||
PythonDependency(
|
||||
environment=environment_development, module=__name__,
|
||||
name='django-debug-toolbar', version_string='==1.11'
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.http import QueryDict
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.six import PY3
|
||||
|
||||
|
||||
class URL(object):
|
||||
@@ -20,9 +21,7 @@ class URL(object):
|
||||
|
||||
def to_string(self):
|
||||
if self._args.keys():
|
||||
query = force_bytes(
|
||||
'?{}'.format(self._args.urlencode())
|
||||
)
|
||||
query = '?{}'.format(self._args.urlencode())
|
||||
else:
|
||||
query = ''
|
||||
|
||||
@@ -31,6 +30,9 @@ class URL(object):
|
||||
else:
|
||||
path = ''
|
||||
|
||||
result = force_bytes('{}{}'.format(path, query))
|
||||
result = '{}{}'.format(path, query)
|
||||
|
||||
if PY3:
|
||||
return result
|
||||
else:
|
||||
return force_bytes(result)
|
||||
|
||||
@@ -35,8 +35,14 @@ icon_menu_about = Icon(
|
||||
icon_menu_user = Icon(
|
||||
driver_name='fontawesome', symbol='user-circle'
|
||||
)
|
||||
icon_object_error_list_with_icon = Icon(
|
||||
driver_name='fontawesome', symbol='lock'
|
||||
icon_object_errors = Icon(
|
||||
driver_name='fontawesome', symbol='exclamation-triangle'
|
||||
)
|
||||
icon_object_error_list = Icon(
|
||||
driver_name='fontawesome', symbol='exclamation-triangle'
|
||||
)
|
||||
icon_object_error_list_clear = Icon(
|
||||
driver_name='fontawesome', symbol='times'
|
||||
)
|
||||
icon_ok = Icon(
|
||||
driver_name='fontawesome', symbol='check'
|
||||
|
||||
@@ -8,8 +8,8 @@ from mayan.apps.navigation.classes import Link
|
||||
from .icons import (
|
||||
icon_about, icon_current_user_locale_profile_details,
|
||||
icon_current_user_locale_profile_edit, icon_documentation,
|
||||
icon_forum, icon_license, icon_object_error_list_with_icon,
|
||||
icon_setup, icon_source_code, icon_support, icon_tools
|
||||
icon_forum, icon_license, icon_setup, icon_source_code, icon_support,
|
||||
icon_tools
|
||||
)
|
||||
from .permissions_runtime import permission_error_log_view
|
||||
|
||||
@@ -50,21 +50,17 @@ link_documentation = Link(
|
||||
text=_('Documentation'), url='https://docs.mayan-edms.com'
|
||||
)
|
||||
link_object_error_list = Link(
|
||||
icon_class_path='mayan.apps.common.icons.icon_object_error_list',
|
||||
kwargs=get_kwargs_factory('resolved_object'),
|
||||
permissions=(permission_error_log_view,), text=_('Errors'),
|
||||
view='common:object_error_list',
|
||||
)
|
||||
link_object_error_list_clear = Link(
|
||||
icon_class_path='mayan.apps.common.icons.icon_object_error_list_clear',
|
||||
kwargs=get_kwargs_factory('resolved_object'),
|
||||
permissions=(permission_error_log_view,), text=_('Clear all'),
|
||||
view='common:object_error_list_clear',
|
||||
)
|
||||
link_object_error_list_with_icon = Link(
|
||||
kwargs=get_kwargs_factory('resolved_object'),
|
||||
icon_class=icon_object_error_list_with_icon,
|
||||
permissions=(permission_error_log_view,), text=_('Errors'),
|
||||
view='common:error_list',
|
||||
)
|
||||
link_forum = Link(
|
||||
icon_class=icon_forum, tags='new_window', text=_('Forum'),
|
||||
url='https://forum.mayan-edms.com'
|
||||
|
||||
@@ -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)
|
||||
@@ -28,8 +28,8 @@ class Command(management.BaseCommand):
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-javascript', action='store_true', dest='no_javascript',
|
||||
help='Don\'t download the JavaScript dependencies.',
|
||||
'--no-dependencies', action='store_true', dest='no_dependencies',
|
||||
help='Don\'t download dependencies.',
|
||||
)
|
||||
|
||||
def initialize_system(self, force=False):
|
||||
@@ -88,9 +88,9 @@ class Command(management.BaseCommand):
|
||||
self.initialize_system(force=options.get('force', False))
|
||||
pre_initial_setup.send(sender=self)
|
||||
|
||||
if not options.get('no_javascript', False):
|
||||
if not options.get('no_dependencies', False):
|
||||
management.call_command(
|
||||
command_name='installjavascript', interactive=False
|
||||
command_name='installdependencies', interactive=False
|
||||
)
|
||||
|
||||
management.call_command(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user