Merge remote-tracking branch 'origin/versions/micro' into merges/micro_to_minor

Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-11-02 02:59:39 -04:00
22 changed files with 242 additions and 91 deletions

View File

@@ -173,7 +173,8 @@ test-mysql:
script:
- apt-get install -qq libmysqlclient-dev mysql-client
- . venv/bin/activate
- pip install mysqlclient
- set -a && . ./config.env && set +a
- pip install mysqlclient==$PYTHON_MYSQL_VERSION
- 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
tags:
@@ -189,7 +190,8 @@ test-postgres:
script:
- apt-get install -qq libpq-dev
- . venv/bin/activate
- pip install psycopg2
- set -a && . ./config.env && set +a
- pip install psycopg2==$PYTHON_PSYCOPG2_VERSION
- python manage.py test --mayan-apps --settings=mayan.settings.testing.gitlab-ci.db_postgres --nomigrations
tags:
- postgres

View File

@@ -137,6 +137,19 @@
- Add makefile target to launch a production staging Docker image.
- Improve duplicated document list view logic to not show
documents with trashed duplicates.
- Backport Docker composer makefile targets.
- Add PermissionTestCaseMixin and SmartSettingTestCaseMixin to better
organize cache invalidation of both apps for tests.
- Add a version attribute to setting namespace. These are dumped
as SMART_SETTINGS_NAMESPACES.
- Add savesettings command.
- Add extra logging to the IMAP email source. GitLab issue #682.
Thanks to Patrick Hütter (@PatrickHuetter) for the report.
- Rename all instances of the IMAP server from mailbox to
server for clarity.
- Add book link in the about menu.
- Add unknown exception handling when checking for the latest
version.
3.2.8 (2019-10-01)
==================

View File

@@ -1,3 +1,5 @@
#!make
include config.env
.PHONY: clean-pyc clean-build
DOCKER_MYSQL_IMAGE = mysql:8.0
@@ -5,11 +7,6 @@ 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
@@ -43,7 +40,7 @@ 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 $(DOCKER_POSTGRES_IMAGE)
docker run -d --name test-postgres -p 5432:5432 -v test-postgres:/var/lib/postgresql/data $(DOCKER_POSTGRES_IMAGE_VERSION)
sudo apt-get install -q libpq-dev
pip install psycopg2==$(PYTHON_PSYCOPG2_VERSION)
while ! nc -z 127.0.0.1 5432; do sleep 1; done
@@ -63,7 +60,7 @@ 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 $(DOCKER_MYSQL_IMAGE)
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_VERSION)
sudo apt-get install -q libmysqlclient-dev mysql-client
pip install mysqlclient==$(PYTHON_MYSQL_VERSION)
while ! nc -z 127.0.0.1 3306; do sleep 1; done
@@ -85,9 +82,9 @@ 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 $(DOCKER_ORACLE_IMAGE)
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_VERSION)
# https://gist.github.com/kimus/10012910
pip install cx_Oracle
pip install cx_Oracle==$(PYTHON_ORACLE_VERSION)
while ! nc -z 127.0.0.1 49161; do sleep 1; done
sleep 10
@@ -256,8 +253,8 @@ 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 $(DOCKER_REDIS_IMAGE)
docker run -d --name postgres -p 5432:5432 $(DOCKER_POSTGRES_IMAGE)
docker run -d --name redis -p 6379:6379 $(DOCKER_REDIS_IMAGE_VERSION)
docker run -d --name postgres -p 5432:5432 $(DOCKER_POSTGRES_IMAGE_VERSION)
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
@@ -275,7 +272,7 @@ test-with-docker-worker: ## Launch a worker instance that uses the production-li
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 $(DOCKER_MYSQL_IMAGE)
docker run -d --name mysql -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=True -e MYSQL_DATABASE=mayan_edms $(DOCKER_MYSQL_IMAGE_VERSION)
while ! nc -z 127.0.0.1 3306; do sleep 1; done
docker-mysql-off: ## Stop and delete the MySQL Docker container.
@@ -283,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 $(DOCKER_POSTGRES_IMAGE)
docker run -d --name postgres -p 5432:5432 $(DOCKER_POSTGRES_IMAGE_VERSION)
while ! nc -z 127.0.0.1 5432; do sleep 1; done
docker-postgres-off: ## Stop and delete the PostgreSQL Docker container.

12
config.env Normal file
View File

@@ -0,0 +1,12 @@
DOCKER_MYSQL_IMAGE_VERSION=mysql:8.0
DOCKER_ORACLE_IMAGE=wnameless/oracle-xe-11g
DOCKER_POSTGRES_IMAGE_VERSION=postgres:9.6-alpine
DOCKER_RABBITMQ_IMAGE_VERSION=rabbitmq:3
DOCKER_REDIS_IMAGE_VERSION=redis:5.0-alpine
PYTHON_FLOWER_VERSION=0.9.3
PYTHON_LIBRABBITMQ_VERSION=2.0.0
PYTHON_MYSQL_VERSION=1.4.4
PYTHON_ORACLE_VERSION=7.2.3
PYTHON_PSYCOPG2_VERSION=2.8.3
PYTHON_PSUTIL_VERSION=5.6.2
PYTHON_REDIS_VERSION=3.2.1

View File

@@ -8,6 +8,8 @@ FROM debian:10.0-slim as BASE_IMAGE
LABEL maintainer="Roberto Rosario roberto.rosario@mayan-edms.com"
COPY config.env /config.env
ENV PYTHONUNBUFFERED=1 \
LC_ALL=C.UTF-8 \
PROJECT_INSTALL_DIR=/opt/mayan-edms
@@ -104,18 +106,22 @@ apt-get install -y --no-install-recommends \
&& chown -R mayan:mayan /src
USER mayan
RUN python3 -m venv "${PROJECT_INSTALL_DIR}" \
RUN set -a \
&& . /config.env \
&& set +a \
&& python3 -m venv "${PROJECT_INSTALL_DIR}" \
&& . "${PROJECT_INSTALL_DIR}/bin/activate" \
&& 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 \
librabbitmq==$PYTHON_LIBRABBITMQ_VERSION \
mysql-python==$PYTHON_MYSQL_VERSION \
psycopg2==$PYTHON_PSYCOPG2_VERSION \
redis==$PYTHON_REDIS_VERSION \
flower==$PYTHON_FLOWER_VERSION \
# 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 \
psutil==5.6.2 \
psutil==$PYTHON_PSUTIL_VERSION \
; fi \
# Install the Python packages needed to build Mayan EDMS
&& pip install --no-cache-dir -r /src/requirements/build.txt \

View File

@@ -1,3 +1,6 @@
#!make
include config.env
HOST_IP = `/sbin/ip route|awk '/docker0/ { print $$9 }'`
APT_PROXY ?= $(HOST_IP):3142
CONSOLE_COLUMNS ?= `echo $$(tput cols)`
@@ -51,13 +54,13 @@ docker-staging-container-postgresql-start:
-e POSTGRES_DB=mayan \
-e POSTGRES_PASSWORD=mayanuserpass \
-v mayan-staging-postgres:/var/lib/postgresql/data \
postgres:9.6-alpine
$(DOCKER_POSTGRES_IMAGE_VERSION)
docker-staging-container-redis-start:
docker run -d \
--name mayan-staging-redis \
--network=mayan-staging \
redis:5.0-alpine \
$(DOCKER_REDIS_IMAGE_VERSION) \
redis-server \
--databases \
"2" \

View File

@@ -59,7 +59,7 @@ Example::
-e POSTGRES_DB=mayan \
-e POSTGRES_PASSWORD=mayanuserpass \
-v /docker-volumes/mayan-edms/postgres-new:/var/lib/postgresql/data \
-d postgres:9.6
|DOCKER_POSTGRES_IMAGE_VERSION|
docker exec -i mayan-edms-pg-new pg_restore -U mayan -d mayan < 2018-06-07_17-09-34.dump

View File

@@ -84,14 +84,14 @@ For another setup that offers more performance and scalability refer to the
------------------------------------------------------
::
sudo -u mayan /opt/mayan-edms/bin/pip install --no-cache-dir --no-use-pep517 psycopg2==2.7.3.2 redis==2.10.6
sudo -u mayan /opt/mayan-edms/bin/pip install --no-cache-dir --no-use-pep517 psycopg2==|PYTHON_PSYCOPG2_VERSION| redis==|PYTHON_REDIS_VERSION|
.. note::
Platforms with the ARM CPU might also need additional requirements.
::
sudo -u mayan /opt/mayan-edms/bin/pip install --no-cache-dir --no-use-pep517 psutil==5.6.2
sudo -u mayan /opt/mayan-edms/bin/pip install --no-cache-dir --no-use-pep517 psutil==|PYTHON_PSUTIL_VERSION|
8. Create the database for the installation:
@@ -204,7 +204,7 @@ of a restart or power failure. The Gunicorn workers are increased to 3.
------------------------------------------
::
sudo -u mayan /opt/mayan-edms/bin/pip install --no-cache-dir --no-use-pep517 librabbitmq==2.0.0
sudo -u mayan /opt/mayan-edms/bin/pip install --no-cache-dir --no-use-pep517 librabbitmq==|PYTHON_LIBRABBITMQ_VERSION|
3. Create the RabbitMQ user and vhost:

View File

@@ -247,7 +247,7 @@ Mayan EDMS follows a simplified model layout based on Vincent Driessen's
Working branches for unfinished and unmerged feature. Likely unstable,
don't use in production. Once the feature is complete, it is merged
into one of the versions branches and deleted.
Special branches:
``releases/all``
@@ -317,7 +317,7 @@ Steps to deploy a development version
$ git clone https://gitlab.com/mayan-edms/mayan-edms.git
$ cd mayan-edms
$ git checkout development
$ git checkout <Corresponding branch>
$ virtualenv venv
$ source venv/bin/activate
$ pip install -r requirements.txt
@@ -558,3 +558,10 @@ Manual release
::
make release-via-docker-ubuntu
Other steps
-----------
#. Update the contrib/scripts/install/docker.sh values
#. Upload contrib/scripts/install/docker.sh to https://get.mayan-edms.com

View File

@@ -23,7 +23,7 @@ tag here, remember to do so in the next steps also.::
Then download version 9.6 of the Docker PostgreSQL image::
docker pull postgres:9.6-alpine
docker pull |DOCKER_POSTGRES_IMAGE_VERSION|
Create and run a PostgreSQL container::
@@ -35,7 +35,7 @@ Create and run a PostgreSQL container::
-e POSTGRES_DB=mayan \
-e POSTGRES_PASSWORD=mayanuserpass \
-v /docker-volumes/mayan-edms/postgres:/var/lib/postgresql/data \
-d postgres:9.6-alpine
|DOCKER_POSTGRES_IMAGE_VERSION|
The PostgreSQL container will have one database named ``mayan``, with an user
named ``mayan`` too, with a password of ``mayanuserpass``. The container will
@@ -92,7 +92,7 @@ binding (``-p 5432:5432``)::
-e POSTGRES_DB=mayan \
-e POSTGRES_PASSWORD=mayanuserpass \
-v /docker-volumes/mayan-edms/postgres:/var/lib/postgresql/data \
-d postgres:9.6-alpine
|DOCKER_POSTGRES_IMAGE_VERSION|
Launch the Mayan EDMS container with the network option and change the
database hostname to the PostgreSQL container name (``mayan-edms-postgres``)

View File

@@ -92,7 +92,7 @@ section for the required changes.
For the Docker image, launch a separate RabbitMQ container
(https://hub.docker.com/_/rabbitmq/)::
docker run -d --name mayan-edms-rabbitmq -e RABBITMQ_DEFAULT_USER=mayan -e RABBITMQ_DEFAULT_PASS=mayanrabbitmqpassword -e RABBITMQ_DEFAULT_VHOST=mayan rabbitmq:3
docker run -d --name mayan-edms-rabbitmq -e RABBITMQ_DEFAULT_USER=mayan -e RABBITMQ_DEFAULT_PASS=mayanrabbitmqpassword -e RABBITMQ_DEFAULT_VHOST=mayan |DOCKER_RABBITMQ_IMAGE_VERSION|
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

View File

@@ -29,12 +29,12 @@ sys.path.append(
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
#extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
#extensions = ["djangodocs", "sphinx.ext.intersphinx"]
# extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
# extensions = ["djangodocs", "sphinx.ext.intersphinx"]
extensions = [
'sphinx.ext.extlinks', 'sphinxcontrib.blockdiag',
'sphinxcontrib.spelling'
@@ -72,20 +72,20 @@ release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
@@ -102,7 +102,7 @@ show_authors = False
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
@@ -119,23 +119,23 @@ html_theme_options = {
}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -148,40 +148,40 @@ html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
html_show_sourcelink = False
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'MayanEDMSdoc'
@@ -191,40 +191,42 @@ html_show_sphinx = False
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'MayanEDMS.tex', 'Mayan EDMS Documentation',
mayan.__author__, 'manual'),
(
'index', 'MayanEDMS.tex', 'Mayan EDMS Documentation',
mayan.__author__, 'manual'
),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
@@ -255,5 +257,25 @@ extlinks = {
}
def _load_env_file(filename='../config.env'):
result = []
with open(filename) as file_object:
for line in file_object:
if not line.startswith('#'):
key, value = line.strip().split('=')
result.append(('|{}|'.format(key), value))
return result
def GlobalSubstitution(app, docname, source):
for old, new in global_subtitutions:
source[0] = source[0].replace(old, new)
def setup(app):
app.add_stylesheet('css/custom.css')
app.connect('source-read', GlobalSubstitution)
global_subtitutions = _load_env_file()

View File

@@ -22,7 +22,7 @@ from .handlers import (
handler_user_locale_profile_session_config, handler_user_locale_profile_create
)
from .links import (
link_about, link_current_user_locale_profile_edit, link_license,
link_about, link_book, link_current_user_locale_profile_edit, link_license,
link_object_error_list_clear, link_setup, link_tools
)
@@ -109,7 +109,7 @@ class CommonApp(MayanAppConfig):
menu_about.bind_links(
links=(
link_tools, link_setup, link_about, link_license,
link_tools, link_setup, link_about, link_book, link_license,
)
)

View File

@@ -11,6 +11,7 @@ icon_add_all = Icon(
)
icon_assign_remove_add = Icon(driver_name='fontawesome', symbol='plus')
icon_assign_remove_remove = Icon(driver_name='fontawesome', symbol='minus')
icon_book = Icon(driver_name='fontawesome', symbol='book')
icon_confirm_form_submit = Icon(driver_name='fontawesome', symbol='check')
icon_confirm_form_cancel = Icon(driver_name='fontawesome', symbol='times')
icon_current_user_locale_profile_details = Icon(

View File

@@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _
from mayan.apps.navigation.classes import Link
from .icons import (
icon_about, icon_current_user_locale_profile_details,
icon_about, icon_book, icon_current_user_locale_profile_details,
icon_current_user_locale_profile_edit, icon_documentation,
icon_forum, icon_license, icon_setup, icon_source_code, icon_support,
icon_tools
@@ -35,6 +35,10 @@ def get_kwargs_factory(variable_name):
link_about = Link(
icon_class=icon_about, text=_('About this'), view='common:about_view'
)
link_book = Link(
icon_class=icon_book, tags='new_window', text=_('Get the book'),
url='https://mayan-edms.com/book/'
)
link_current_user_locale_profile_details = Link(
icon_class=icon_current_user_locale_profile_details,
text=_('Locale profile'),

View File

@@ -6,8 +6,10 @@ from django_downloadview import assert_download_response
from mayan.apps.acls.tests.mixins import ACLTestCaseMixin
from mayan.apps.converter.tests.mixins import LayerTestCaseMixin
from mayan.apps.permissions.classes import Permission
from mayan.apps.smart_settings.classes import Namespace
from mayan.apps.permissions.tests.mixins import PermissionTestCaseMixin
from mayan.apps.smart_settings.tests.mixins import SmartSettingsTestCaseMixin
from mayan.apps.user_management.tests.mixins import UserTestMixin
from .mixins import (
@@ -22,19 +24,15 @@ from .mixins import (
class BaseTestCase(
LayerTestCaseMixin, SilenceLoggerTestCaseMixin, ConnectionsCheckTestCaseMixin,
RandomPrimaryKeyModelMonkeyPatchMixin, ACLTestCaseMixin,
ModelTestCaseMixin, OpenFileCheckTestCaseMixin,
TempfileCheckTestCasekMixin, UserTestMixin, TestCase
ModelTestCaseMixin, OpenFileCheckTestCaseMixin, PermissionTestCaseMixin,
SmartSettingsTestCaseMixin, TempfileCheckTestCasekMixin, UserTestMixin,
TestCase
):
"""
This is the most basic test case class any test in the project should use.
"""
assert_download_response = assert_download_response
def setUp(self):
super(BaseTestCase, self).setUp()
Namespace.invalidate_cache_all()
Permission.invalidate_cache()
class GenericViewTestCase(
ClientMethodsTestCaseMixin, ContentTypeCheckTestCaseMixin,

View File

@@ -28,6 +28,12 @@ class CheckVersionView(SimpleView):
'It is not possible to determine the latest version '
'available.'
)
except Exception as exception:
message = _(
'Unexpected error trying to determine the latest version '
'available. Make sure your installation has a connection to '
'the internet; %s'
) % exception
else:
message = _('Your version is up-to-date.')

View File

@@ -1,6 +1,6 @@
from __future__ import unicode_literals
from ..classes import PermissionNamespace
from ..classes import Permission, PermissionNamespace
from ..models import Role
from .literals import (
@@ -45,6 +45,12 @@ class PermissionTestMixin(object):
)
class PermissionTestCaseMixin(object):
def setUp(self):
super(PermissionTestCaseMixin, self).setUp()
Permission.invalidate_cache()
class RoleAPIViewTestMixin(object):
def _request_test_role_create_api_view(self, extra_data=None):
data = {

View File

@@ -21,6 +21,7 @@ from mayan.apps.common.serialization import yaml_dump, yaml_load
from .utils import read_configuration_file
logger = logging.getLogger(__name__)
SMART_SETTINGS_NAMESPACES_NAME = 'SMART_SETTINGS_NAMESPACES'
@python_2_unicode_compatible
@@ -39,26 +40,35 @@ class Namespace(object):
exception
)
@classmethod
def get(cls, name):
return cls._registry[name]
@classmethod
def get_all(cls):
return sorted(cls._registry.values(), key=lambda x: x.label)
@classmethod
def get(cls, name):
return cls._registry[name]
def get_namespace_config(cls, name):
return cls.get_namespaces_config().get(name, {})
@classmethod
def get_namespaces_config(cls):
return getattr(settings, SMART_SETTINGS_NAMESPACES_NAME, {})
@classmethod
def invalidate_cache_all(cls):
for namespace in cls.get_all():
namespace.invalidate_cache()
def __init__(self, name, label):
def __init__(self, name, label, version='0001'):
if name in self.__class__._registry:
raise Exception(
'Namespace names must be unique; "%s" already exists.' % name
)
self.name = name
self.label = label
self.version = version
self.__class__._registry[name] = self
self._settings = []
@@ -68,6 +78,9 @@ class Namespace(object):
def add_setting(self, **kwargs):
return Setting(namespace=self, **kwargs)
def get_config_version(self):
return Namespace.get_namespace_config(name=self.name).get('version', None)
def invalidate_cache(self):
for setting in self._settings:
setting.invalidate_cache()
@@ -123,7 +136,18 @@ class Setting(object):
def dump_data(cls, filter_term=None, namespace=None):
dictionary = {}
if not namespace:
namespace_dictionary = {}
for _namespace in Namespace.get_all():
namespace_dictionary[_namespace.name] = {
'version': _namespace.version
}
dictionary[SMART_SETTINGS_NAMESPACES_NAME] = namespace_dictionary
for setting in cls.get_all():
# If a namespace is specified, filter the list by that namespace
# otherwise return always True to include all (or not None == True)
if (namespace and setting.namespace.name == namespace) or not namespace:
if (filter_term and filter_term.lower() in setting.global_name.lower()) or not filter_term:
dictionary[setting.global_name] = Setting.express_promises(setting.value)
@@ -157,9 +181,12 @@ class Setting(object):
)
@classmethod
def save_configuration(cls, path=settings.CONFIGURATION_FILEPATH):
def save_configuration(cls, path=None):
if not path:
path = settings.CONFIGURATION_FILEPATH
try:
with open(path, 'w') as file_object:
with open(path, mode='w') as file_object:
file_object.write(cls.dump_data())
except IOError as exception:
if exception.errno == errno.ENOENT:

View File

@@ -0,0 +1,18 @@
from __future__ import unicode_literals
from django.core import management
from ...classes import Setting
class Command(management.BaseCommand):
help = 'Save the current settings into the configuration file.'
def add_arguments(self, parser):
parser.add_argument(
'--filepath', action='store', dest='filepath',
help='Filename and path where to save the configuration file.'
)
def handle(self, *args, **options):
Setting.save_configuration(path=options.get('filepath'))

View File

@@ -5,6 +5,12 @@ from ..classes import Namespace
from .literals import TEST_NAMESPACE_LABEL, TEST_NAMESPACE_NAME
class SmartSettingsTestCaseMixin(object):
def setUp(self):
super(SmartSettingsTestCaseMixin, self).setUp()
Namespace.invalidate_cache_all()
class SmartSettingTestMixin(object):
def _create_test_settings_namespace(self):
try:

View File

@@ -261,7 +261,14 @@ class IMAPEmail(EmailBaseModel):
server = imaplib.IMAP4(host=self.host, port=self.port)
server.login(user=self.username, password=self.password)
server.select(mailbox=self.mailbox)
try:
server.select(mailbox=self.mailbox)
except Exception as exception:
raise SourceException(
'Error selecting mailbox: {}; {}'.format(
self.mailbox, exception
)
)
try:
status, data = server.uid(
@@ -280,10 +287,26 @@ class IMAPEmail(EmailBaseModel):
for uid in uids:
logger.debug('message uid: %s', uid)
status, data = server.uid('FETCH', uid, '(RFC822)')
EmailBaseModel.process_message(
source=self, message_text=data[0][1]
)
try:
status, data = server.uid('FETCH', uid, '(RFC822)')
except Exception as exception:
raise SourceException(
'Error fetching message uid: {}; {}'.format(
uid, exception
)
)
try:
EmailBaseModel.process_message(
source=self, message_text=data[0][1]
)
except Exception as exception:
raise SourceException(
'Error processing message uid: {}; {}'.format(
uid, exception
)
)
if not test:
if self.store_commands: