From: Guillaume Pellerin Date: Thu, 16 Apr 2015 12:53:32 +0000 (+0200) Subject: Merge branch 'dev' into feature/epub X-Git-Tag: 1.6a^2~19^2~2 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=92191d25abd5b5957c60404b7e2a43f5b9a904f7;p=telemeta.git Merge branch 'dev' into feature/epub Conflicts: examples/sandbox/settings.py telemeta/models/collection.py telemeta/models/item.py --- 92191d25abd5b5957c60404b7e2a43f5b9a904f7 diff --cc examples/sandbox/settings.py index 00000000,6a38f201..0b02f2d2 mode 000000,100644..100644 --- a/examples/sandbox/settings.py +++ b/examples/sandbox/settings.py @@@ -1,0 -1,286 +1,287 @@@ + # -*- coding: utf-8 -*- + # Django settings for sandbox project. + + import os, sys + from django.core.urlresolvers import reverse_lazy, reverse + + sys.dont_write_bytecode = True + + DEBUG = True + TEMPLATE_DEBUG = DEBUG + + ALLOWED_HOSTS = ['*'] + + ADMINS = ( + ('Guillaume Pellerin', 'yomguy@parisson.com'), + ) + + MANAGERS = ADMINS + + # Full filesystem path to the project. + PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) + + DATABASES = { + 'default': { + # SQLite config + # 'ENGINE': 'django.db.backends.sqlite', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. + # 'NAME': os.path.join(PROJECT_ROOT, 'telemeta.sql'), # Or path to database file if using sqlite3. + # 'OPTIONS': { + # 'timeout': 60, + # } + + # MySQL config + 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. + 'USER': 'root', # Not used with sqlite3. + 'PASSWORD': 'mysecretpassword', # Not used with sqlite3. + 'NAME': 'sandbox', + 'HOST': 'db', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '3306', # Set to empty string for default. Not used with sqlite3. + } + } + + # Local time zone for this installation. Choices can be found here: + # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name + # although not all choices may be available on all operating systems. + # On Unix systems, a value of None will cause Django to use the same + # timezone as the operating system. + # If running in a Windows environment this must be set to the same as your + # system time zone. + TIME_ZONE = 'Europe/Paris' + + # Language code for this installation. All choices can be found here: + # http://www.i18nguy.com/unicode/language-identifiers.html + #LANGUAGE_CODE = 'fr_FR' + + LANGUAGES = [ ('fr', 'French'), + ('en', 'English'), + ('de', 'German'), + ('zh_CN', 'Simplified Chinese'), + ('ar_TN', 'Arabic'), + ] + + SITE_ID = 1 + + # If you set this to False, Django will make some optimizations so as not + # to load the internationalization machinery. + USE_I18N = True + + # If you set this to False, Django will not format dates, numbers and + # calendars according to the current locale + USE_L10N = True + + # Absolute path to the directory that holds media. + # Example: "/home/media/media.lawrence.com/" + MEDIA_ROOT = PROJECT_ROOT + '/media/' + + if not os.path.exists(MEDIA_ROOT): + os.makedirs(MEDIA_ROOT) + + # URL that handles the media served from MEDIA_ROOT. Make sure to use a + # trailing slash if there is a path component (optional in other cases). + # Examples: "http://media.lawrence.com", "http://example.com/media/" + MEDIA_URL = '/media/' + + # Absolute path to the directory static files should be collected to. + # Don't put anything in this directory yourself; store your static files + # in apps' "static/" subdirectories and in STATICFILES_DIRS. + # Example: "/home/media/media.lawrence.com/static/" + STATIC_ROOT = '/var/www/static' + + # URL prefix for static files. + # Example: "http://media.lawrence.com/static/" + STATIC_URL = '/static/' + + # Additional locations of static files + STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + ) + + # List of finder classes that know how to find static files in + # various locations. + STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + # 'django.contrib.staticfiles.finders.DefaultStorageFinder', + ) + # Make this unique, and don't share it with anybody. + SECRET_KEY = 'a8l7%06wr2k+3=%#*#@#rvop2mmzko)44%7k(zx%lls^ihm9^5' + + # List of callables that know how to import templates from various sources. + TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + # 'django.template.loaders.eggs.Loader', + ) + + + MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.locale.LocaleMiddleware', + # 'pagination.middleware.PaginationMiddleware', + ) + + ROOT_URLCONF = 'urls' + + TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + ) + + INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'suit', + 'django.contrib.admin', + 'django.contrib.staticfiles', + 'django_extensions', + 'telemeta', + 'timeside.player', + 'timeside.server', + 'jsonrpc', + 'south', + 'sorl.thumbnail', + 'timezones', + 'jqchat', + 'ipauth', + 'extra_views', + 'debug_toolbar', + 'bootstrap3', + 'bootstrap_pagination', + 'googletools', + 'registration', ++ 'epub', + 'rest_framework', + 'djcelery', + ) + + TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.core.context_processors.request', + 'django.contrib.auth.context_processors.auth', + "django.core.context_processors.i18n", + "django.core.context_processors.media", + 'django.core.context_processors.static', + 'django.contrib.messages.context_processors.messages', + ) + + + AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', + 'ipauth.backend.RangeBackend', + ) + + SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies" + + TELEMETA_ORGANIZATION = 'Parisson' + TELEMETA_SUBJECTS = ('test', 'telemeta', 'sandbox') + TELEMETA_DESCRIPTION = "Telemeta TEST sandbox" + TELEMETA_LOGO = STATIC_URL + 'telemeta/images/logo_telemeta_2.png' + + TELEMETA_GMAP_KEY = 'ABQIAAAArg7eSfnfTkBRma8glnGrlxRVbMrhnNNvToCbZQtWdaMbZTA_3RRGObu5PDoiBImgalVnnLU2yN4RMA' + + TELEMETA_CACHE_DIR = os.path.join(MEDIA_ROOT, 'cache') + TELEMETA_EXPORT_CACHE_DIR = os.path.join(MEDIA_ROOT, 'export') + TELEMETA_DATA_CACHE_DIR = os.path.join(TELEMETA_CACHE_DIR, 'data') + + TELEMETA_DOWNLOAD_ENABLED = True + TELEMETA_STREAMING_FORMATS = ('mp3', 'ogg') + TELEMETA_DOWNLOAD_FORMATS = ('wav', 'mp3', 'ogg', 'flac') + TELEMETA_PUBLIC_ACCESS_PERIOD = 51 + + TELEMETA_STRICT_CODE = False + + AUTH_PROFILE_MODULE = 'telemeta.userprofile' + SESSION_EXPIRE_AT_BROWSER_CLOSE = False + + LOGIN_URL = '/login/' + LOGIN_REDIRECT_URL = '/desk/lists/' + + EMAIL_HOST = 'localhost' + DEFAULT_FROM_EMAIL = 'webmaster@parisson.com' + + TIMESIDE_DEFAULT_GRAPHER_ID = 'waveform_centroid' + TIMESIDE_DEFAULT_WAVEFORM_SIZES = ['346x130', '640x130'] + TIMESIDE_AUTO_ZOOM = False + + # Settings for django-bootstrap3 + BOOTSTRAP3 = { + 'set_required': True, + 'set_placeholder': False, + 'error_css_class': 'has-error', + 'required_css_class': 'has-warning', + 'javascript_in_head': True, + } + + PAGINATION_SETTINGS = { + 'PAGE_RANGE_DISPLAYED': 10, + 'MARGIN_PAGES_DISPLAYED': 2, + } + + DEBUG_TOOLBAR_PATCH_SETTINGS = False + DEBUG_TOOLBAR_PANELS = [ + 'debug_toolbar.panels.versions.VersionsPanel', + 'debug_toolbar.panels.timer.TimerPanel', + 'debug_toolbar.panels.settings.SettingsPanel', + 'debug_toolbar.panels.headers.HeadersPanel', + 'debug_toolbar.panels.request.RequestPanel', + 'debug_toolbar.panels.sql.SQLPanel', + 'debug_toolbar.panels.staticfiles.StaticFilesPanel', + 'debug_toolbar.panels.templates.TemplatesPanel', + 'debug_toolbar.panels.cache.CachePanel', + 'debug_toolbar.panels.signals.SignalsPanel', + 'debug_toolbar.panels.logging.LoggingPanel', + 'debug_toolbar.panels.redirects.RedirectsPanel', + ] + + SUIT_CONFIG = { + 'ADMIN_NAME': 'Telemeta Admin' + } + + + # A sample logging configuration. The only tangible logging + # performed by this configuration is to send an email to + # the site admins on every HTTP 500 error when DEBUG=False. + # See http://docs.djangoproject.com/en/dev/topics/logging for + # more details on how to customize your logging configuration. + LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + } + } + + # replace rabbitmq by localhost if you start your app outside docker-compose + BROKER_URL = 'amqp://guest:guest@rabbitmq//' + + CELERY_IMPORTS = ("timeside.server.tasks",) + CELERY_RESULT_BACKEND='djcelery.backends.database:DatabaseBackend' + CELERY_TASK_SERIALIZER = "json" + CELERY_ACCEPT_CONTENT = ['application/json'] + + from celery_app import app diff --cc telemeta/models/collection.py index d3be102a,18faa252..e70fd29d --- a/telemeta/models/collection.py +++ b/telemeta/models/collection.py @@@ -260,8 -266,11 +266,12 @@@ class MediaCollection(MediaResource) return metadata + def get_json(self): + import json + return json.dumps(self.to_dict_with_more()) + + class MediaCollectionRelated(MediaRelated): "Collection related media" diff --cc telemeta/templates/telemeta/collection_epub.html index cc9ea38c,00000000..1bc24e12 mode 100644,000000..100644 --- a/telemeta/templates/telemeta/collection_epub.html +++ b/telemeta/templates/telemeta/collection_epub.html @@@ -1,28 -1,0 +1,28 @@@ + +{% for item in items %} + + + + - + +
+ {% for image in item.related.all %} + {% if 'image' in image.mime_type %} +
- ++ +
+ {% endif %} + {% endfor %} + +
++ + + {{ item.old_code }} : {{ item.title }} (p. {{ item.track }}) + +
+ +
+ +
+ +{% endfor %} diff --cc telemeta/views/core.py index 2e503eb0,2e503eb0..a8f518ab --- a/telemeta/views/core.py +++ b/telemeta/views/core.py @@@ -76,6 -76,6 +76,7 @@@ from django.utils.translation import ug from django.forms.models import model_to_dict from django.views.generic.edit import DeletionMixin, BaseDeleteView from django.contrib.sites.models import Site ++from django.template.defaultfilters import slugify from telemeta.models import * import telemeta.models diff --cc telemeta/views/resource.py index 11d0b287,dc84ccb0..2db47254 --- a/telemeta/views/resource.py +++ b/telemeta/views/resource.py @@@ -348,101 -348,3 +348,109 @@@ class ResourceEditView(ResourceSingleMi def dispatch(self, *args, **kwargs): return super(ResourceEditView, self).dispatch(*args, **kwargs) + ++def cleanup_path(path): ++ new_path = [] ++ for dir in path.split(os.sep): ++ new_path.append(slugify(dir)) ++ return os.sep.join(new_path) ++ ++ +class CorpusEpubView(View): + + model = MediaCorpus + + def get_object(self): + return MediaCorpus.objects.get(public_id=self.kwargs['public_id']) + + def get(self, request, *args, **kwargs): + """ + Stream an Epub file of collection data + """ + from collections import OrderedDict + from ebooklib import epub + from django.template.loader import render_to_string + + book = epub.EpubBook() + corpus = self.get_object() + local_path = os.path.dirname(__file__) + css = os.sep.join([local_path, '..', 'static', 'telemeta', 'css', 'telemeta_epub.css']) + collection_template = os.sep.join([local_path, '..', 'templates', 'telemeta', 'collection_epub.html']) + site = Site.objects.get_current() + + # add metadata + book.set_identifier(corpus.public_id) + book.set_title(corpus.title) + book.set_language('fr') + book.add_author(corpus.descriptions) + + + # add cover image + # for media in corpus.related.all(): + # if 'cover' in media.title or 'Cover' in media.title: + # book.set_cover("cover.jpg", open(media.file.path, 'r').read()) + # break + + chapters = [] + for collection in corpus.children.all(): + items = {} + for item in collection.items.all(): + id = item.old_code.split(' ') + if len(id) > 1: + id = id[1] + items[item] = int(id.split('.')[1]) + items = OrderedDict(sorted(items.items(), key=lambda t: t[1])) + + for item in items: + if item.file: + audio = open(item.file.path, 'r') - epub_item = epub.EpubItem(file_name=str(item.file), content=audio.read()) ++ filename = str(item.file) ++ epub_item = epub.EpubItem(file_name=cleanup_path(str(item.file)), content=audio.read()) + book.add_item(epub_item) + for related in item.related.all(): + if 'image' in related.mime_type: + image = open(related.file.path, 'r') - epub_item = epub.EpubItem(file_name=str(related.file), content=image.read()) ++ epub_item = epub.EpubItem(file_name=cleanup_path((str(related.file)), content=image.read()) + book.add_item(epub_item) + context = {'collection': collection, 'site': site, 'items': items} + c = epub.EpubHtml(title=collection.title, file_name=collection.code + '.xhtml', lang='fr') + c.content = render_to_string(collection_template, context) + chapters.append(c) + # add chapters to the book + book.add_item(c) + + # create table of contents + # - add manual link + # - add section + # - add auto created links to chaptersfesse + + book.toc = (( chapters )) + + # add navigation files + book.add_item(epub.EpubNcx()) + book.add_item(epub.EpubNav()) + + + # add css style + style = open(css, 'r') + nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style.read()) + book.add_item(nav_css) + + # create spin, add cover page as first page + chapters.insert(0,'nav') + chapters.insert(0,'cover') + book.spine = chapters + + # create epub file + filename = '/tmp/test.epub' + epub.write_epub(filename, book, {}) + epub_file = open(filename, 'r') + + response = HttpResponse(epub_file.read(), content_type='application/epub+zip') + response['Content-Disposition'] = "attachment; filename=%s.%s" % \ + (collection.code, 'epub') + return response + + @method_decorator(login_required) + def dispatch(self, *args, **kwargs): + return super(CorpusEpubView, self).dispatch(*args, **kwargs)