Skip to content
This repository has been archived by the owner on Oct 24, 2018. It is now read-only.

Commit

Permalink
Merge 430520e into bbccafc
Browse files Browse the repository at this point in the history
  • Loading branch information
inkhey committed Jul 24, 2018
2 parents bbccafc + 430520e commit 8f5c1a3
Show file tree
Hide file tree
Showing 13 changed files with 2,106 additions and 6 deletions.
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ python:
- "3.5"
- "3.6"

addons:
apt:
packages:
- libreoffice
- imagemagick
- libmagickwand-dev
- ghostscript
services:
- docker
- redis-server
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ on Debian Stretch (9) with sudo:
sudo apt install git
sudo apt install python3 python3-venv python3-dev python3-pip
sudo apt install redis-server
sudo apt install zlib1g-dev libjpeg-dev

for better preview support:

sudo apt install libreoffice # most office documents file and text format
sudo apt install inkscape # for .svg files.

### Get the source ###

Expand Down
11 changes: 11 additions & 0 deletions development.ini.sample
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,17 @@ wsgidav.config_path = %(here)s/wsgidav.conf
## Do not set http:// prefix.
# wsgidav.client.base_url = 127.0.0.1:<WSGIDAV_PORT>

### Preview
## You can parametrized allowed jpg preview dimension list, if not set, default
## is 256x256. First {width}x{length} items is default preview dimensions.
## all items should be separated by ',' and you should be really careful to do
## set anything else than '{int}x{int}' item and ', ' separator
# preview.jpg.allowed_dims = 256x256,1000x1000
## Preview dimensions can be set as restricted, if set as restricted, access
## endpoint to to get any other preview dimensions than allowed_dims will
## return error
# preview.jpg.restricted_dims = True

###
# wsgi server configuration
###
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
'filedepot',
'babel',
'python-slugify',
'preview-generator',
# mail-notifier
'mako',
'lxml',
Expand All @@ -48,7 +49,8 @@
'pytest-cov',
'pep8',
'mypy',
'requests'
'requests',
'Pillow'
]

mysql_require = [
Expand Down
5 changes: 5 additions & 0 deletions tracim/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-


try: # Python 3.5+
from http import HTTPStatus
except ImportError:
Expand Down Expand Up @@ -27,6 +29,7 @@
from tracim.views.core_api.user_controller import UserController
from tracim.views.core_api.workspace_controller import WorkspaceController
from tracim.views.contents_api.comment_controller import CommentController
from tracim.views.contents_api.file_controller import FileController
from tracim.views.errors import ErrorSchema
from tracim.exceptions import NotAuthenticated
from tracim.exceptions import InvalidId
Expand Down Expand Up @@ -109,13 +112,15 @@ def web(global_config, **local_settings):
comment_controller = CommentController()
html_document_controller = HTMLDocumentController()
thread_controller = ThreadController()
file_controller = FileController()
configurator.include(session_controller.bind, route_prefix=BASE_API_V2)
configurator.include(system_controller.bind, route_prefix=BASE_API_V2)
configurator.include(user_controller.bind, route_prefix=BASE_API_V2)
configurator.include(workspace_controller.bind, route_prefix=BASE_API_V2)
configurator.include(comment_controller.bind, route_prefix=BASE_API_V2)
configurator.include(html_document_controller.bind, route_prefix=BASE_API_V2) # nopep8
configurator.include(thread_controller.bind, route_prefix=BASE_API_V2)
configurator.include(file_controller.bind, route_prefix=BASE_API_V2)

hapic.add_documentation_view(
'/api/v2/doc',
Expand Down
33 changes: 33 additions & 0 deletions tracim/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,26 @@ def __init__(self, settings):
# self.RADICALE_CLIENT_BASE_URL_HOST,
# self.RADICALE_CLIENT_BASE_URL_PREFIX,
# )
self.PREVIEW_JPG_RESTRICTED_DIMS = asbool(settings.get(
'preview.jpg.restricted_dims', False
))
preview_jpg_allowed_dims_str = settings.get('preview.jpg.allowed_dims', '') # nopep8
allowed_dims = []
if preview_jpg_allowed_dims_str:
for sizes in preview_jpg_allowed_dims_str.split(','):
parts = sizes.split('x')
assert len(parts) == 2
width, height = parts
assert width.isdecimal()
assert height.isdecimal()
size = PreviewDim(int(width), int(height))
allowed_dims.append(size)

if not allowed_dims:
size = PreviewDim(256, 256)
allowed_dims.append(size)

self.PREVIEW_JPG_ALLOWED_DIMS = allowed_dims

def configure_filedepot(self):
depot_storage_name = self.DEPOT_STORAGE_NAME
Expand All @@ -427,3 +447,16 @@ class CST(object):

TREEVIEW_FOLDERS = 'folders'
TREEVIEW_ALL = 'all'


class PreviewDim(object):

def __init__(self, width: int, height: int) -> None:
self.width = width
self.height = height

def __repr__(self):
return "<PreviewDim width:{width} height:{height}>".format(
width=self.width,
height=self.height,
)
12 changes: 12 additions & 0 deletions tracim/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,15 @@ class EmptyCommentContentNotAllowed(EmptyValueNotAllowed):

class ParentNotFound(NotFound):
pass


class RevisionDoesNotMatchThisContent(TracimException):
pass


class PageOfPreviewNotFound(NotFound):
pass


class PreviewDimNotAllowed(TracimException):
pass
114 changes: 111 additions & 3 deletions tracim/lib/core/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from operator import itemgetter

import transaction
from preview_generator.manager import PreviewManager
from sqlalchemy import func
from sqlalchemy.orm import Query
from depot.manager import DepotManager
Expand All @@ -25,6 +26,9 @@
from tracim.lib.utils.utils import cmp_to_key
from tracim.lib.core.notifications import NotifierFactory
from tracim.exceptions import SameValueError
from tracim.exceptions import PageOfPreviewNotFound
from tracim.exceptions import PreviewDimNotAllowed
from tracim.exceptions import RevisionDoesNotMatchThisContent
from tracim.exceptions import EmptyCommentContentNotAllowed
from tracim.exceptions import EmptyLabelNotAllowed
from tracim.exceptions import ContentNotFound
Expand All @@ -42,7 +46,8 @@
from tracim.models.data import UserRoleInWorkspace
from tracim.models.data import Workspace
from tracim.lib.utils.translation import fake_translator as _
from tracim.models.context_models import RevisionInContext
from tracim.models.context_models import RevisionInContext, \
PreviewAllowedDim
from tracim.models.context_models import ContentInContext

__author__ = 'damien'
Expand Down Expand Up @@ -133,6 +138,7 @@ def __init__(
self._show_all_type_of_contents_in_treeview = all_content_in_treeview
self._force_show_all_types = force_show_all_types
self._disable_user_workspaces_filter = disable_user_workspaces_filter
self.preview_manager = PreviewManager(self._config.PREVIEW_CACHE_DIR, create_folder=True) # nopep8

@contextmanager
def show(
Expand Down Expand Up @@ -499,16 +505,24 @@ def get_one(self, content_id: int, content_type: str, workspace: Workspace=None,
raise ContentNotFound('Content "{}" not found in database'.format(content_id)) from exc # nopep8
return content

def get_one_revision(self, revision_id: int = None) -> ContentRevisionRO:
def get_one_revision(self, revision_id: int = None, content: Content= None) -> ContentRevisionRO: # nopep8
"""
This method allow us to get directly any revision with its id
:param revision_id: The content's revision's id that we want to return
:param content: The content related to the revision, if None do not
check if revision is related to this content.
:return: An item Content linked with the correct revision
"""
assert revision_id is not None# DYN_REMOVE

revision = self._session.query(ContentRevisionRO).filter(ContentRevisionRO.revision_id == revision_id).one()

if content and revision.content_id != content.content_id:
raise RevisionDoesNotMatchThisContent(
'revision {revision_id} is not a revision of content {content_id}'.format( # nopep8
revision_id=revision.revision_id,
content_id=content.content_id,
)
)
return revision

# INFO - A.P - 2017-07-03 - python file object getter
Expand Down Expand Up @@ -724,6 +738,100 @@ def filter_query_for_content_label_as_path(
),
))

def get_pdf_preview_path(
self,
content_id: int,
revision_id: int,
page: int
) -> str:
"""
Get pdf preview of revision of content
:param content_id: id of content
:param revision_id: id of content revision
:param page: page number of the preview, useful for multipage content
:return: preview_path as string
"""
file_path = self.get_one_revision_filepath(revision_id)
if page >= self.preview_manager.get_page_nb(file_path):
raise PageOfPreviewNotFound(
'page {page} of content {content_id} does not exist'.format(
page=page,
content_id=content_id
),
)
jpg_preview_path = self.preview_manager.get_pdf_preview(
file_path,
page=page
)
return jpg_preview_path

def get_full_pdf_preview_path(self, revision_id: int) -> str:
"""
Get full(multiple page) pdf preview of revision of content
:param revision_id: id of revision
:return: path of the full pdf preview of this revision
"""
file_path = self.get_one_revision_filepath(revision_id)
pdf_preview_path = self.preview_manager.get_pdf_preview(file_path)
return pdf_preview_path

def get_jpg_preview_allowed_dim(self) -> PreviewAllowedDim:
return PreviewAllowedDim(
self._config.PREVIEW_JPG_RESTRICTED_DIMS,
self._config.PREVIEW_JPG_ALLOWED_DIMS,
)

def get_jpg_preview_path(
self,
content_id: int,
revision_id: int,
page: int,
width: int = None,
height: int = None,
) -> str:
"""
Get jpg preview of revision of content
:param content_id: id of content
:param revision_id: id of content revision
:param page: page number of the preview, useful for multipage content
:param width: width in pixel
:param height: height in pixel
:return: preview_path as string
"""
file_path = self.get_one_revision_filepath(revision_id)
if page >= self.preview_manager.get_page_nb(file_path):
raise Exception(
'page {page} of revision {revision_id} of content {content_id} does not exist'.format( # nopep8
page=page,
revision_id=revision_id,
content_id=content_id,
),
)
if not width and not height:
width = self._config.PREVIEW_JPG_ALLOWED_DIMS[0].width
height = self._config.PREVIEW_JPG_ALLOWED_DIMS[0].height

allowed_dim = False
for preview_dim in self._config.PREVIEW_JPG_ALLOWED_DIMS:
if width == preview_dim.width and height == preview_dim.height:
allowed_dim = True
break

if not allowed_dim and self._config.PREVIEW_JPG_RESTRICTED_DIMS:
raise PreviewDimNotAllowed(
'Size {width}x{height} is not allowed for jpeg preview'.format(
width=width,
height=height,
)
)
jpg_preview_path = self.preview_manager.get_jpeg_preview(
file_path,
page=page,
width=width,
height=height,
)
return jpg_preview_path

def get_all(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> typing.List[Content]:
assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
assert content_type is not None# DYN_REMOVE
Expand Down
Loading

0 comments on commit 8f5c1a3

Please sign in to comment.