From 4e52d50eec36153c0c88bf04eade2f4e595af6fc Mon Sep 17 00:00:00 2001 From: olegpshenichniy Date: Fri, 18 May 2018 17:00:54 +0200 Subject: [PATCH 1/8] BK-2085 Add support for Django 1.11 LTS --- docs/settings.py | 2 +- lib/booki/editor/common.py | 492 ------------------ .../editor/management/commands/bookexport.py | 79 --- .../editor/management/commands/bookrename.py | 52 +- .../editor/management/commands/brokenlinks.py | 82 +-- .../editor/management/commands/confdel.py | 11 +- .../editor/management/commands/confget.py | 32 +- .../editor/management/commands/conflist.py | 24 +- .../editor/management/commands/confset.py | 75 ++- lib/booki/editor/tests.py | 12 +- .../messaging/templatetags/messaging_tags.py | 8 +- .../management/commands/generate_filters.py | 10 +- lib/booktype/apps/account/views.py | 2 +- lib/booktype/apps/convert/tasks.py | 11 +- .../core/management/commands/createuser.py | 30 +- .../management/commands/update_permissions.py | 10 +- .../core/templates/core/booktype_groups.html | 5 - .../apps/core/templatetags/booktype_tags.py | 28 +- .../apps/importer/management/__init__.py | 0 .../importer/management/commands/__init__.py | 0 .../management/commands/bookimport.py | 76 --- .../loadsave/management/commands/bookload.py | 48 +- .../loadsave/management/commands/booksave.py | 42 +- .../reports/management/commands/report.py | 54 +- .../management/commands/reportweekly.py | 58 +-- .../reports/management/commands/signups.py | 2 +- .../themes/management/commands/reset_theme.py | 32 +- .../apps/themes/templatetags/themes.py | 5 +- lib/booktype/convert/epub/converter.py | 4 +- lib/booktype/convert/mpdf/converter.py | 5 +- .../skeleton/base_settings.py.original | 10 +- .../skeleton/prod_settings.py.original | 2 +- .../skeleton/stage_settings.py.original | 2 +- lib/booktypecontrol/forms.py | 4 +- lib/booktypecontrol/views.py | 5 +- requirements/_base.txt | 8 +- scripts/createbooktype | 88 +++- tests/func_settings.py | 2 +- tests/selenium_settings.py | 2 +- tests/settings.py | 2 +- 40 files changed, 381 insertions(+), 1035 deletions(-) delete mode 100644 lib/booki/editor/common.py delete mode 100644 lib/booki/editor/management/commands/bookexport.py delete mode 100644 lib/booktype/apps/core/templates/core/booktype_groups.html delete mode 100644 lib/booktype/apps/importer/management/__init__.py delete mode 100644 lib/booktype/apps/importer/management/commands/__init__.py delete mode 100644 lib/booktype/apps/importer/management/commands/bookimport.py diff --git a/docs/settings.py b/docs/settings.py index 79f7dc492..75efcd99c 100644 --- a/docs/settings.py +++ b/docs/settings.py @@ -38,8 +38,8 @@ 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_celery_results', 'compressor', - 'djcelery', # list of booki apps 'booki.editor', diff --git a/lib/booki/editor/common.py b/lib/booki/editor/common.py deleted file mode 100644 index 8152e44c3..000000000 --- a/lib/booki/editor/common.py +++ /dev/null @@ -1,492 +0,0 @@ -# This file is part of Booktype. -# Copyright (c) 2012 Aleksandar Erkalovic -# -# Booktype is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Booktype is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Booktype. If not, see . - -""" -Some common functions for booki editor. -""" -import tempfile -import urllib2 -from urllib import urlencode -import zipfile -import os, sys -import datetime -import re -import logging -from cStringIO import StringIO -import traceback -import time - -import json - -from lxml import etree, html - -from django import template -from django.template.context import Context -from django.utils.translation import ugettext_lazy as _ - -from booki.editor import models -from booki.bookizip import get_metadata, add_metadata, DC, FM - -from booki.utils.log import logBookHistory, logWarning -from booktype.utils.book import create_book -from booktype.utils.misc import booktype_slugify -from booki.editor.views import getVersion - -from django.conf import settings - -try: - THIS_BOOKI_SERVER = settings.THIS_BOOKI_SERVER - DEFAULT_PUBLISHER = settings.DEFAULT_PUBLISHER -except AttributeError: - THIS_BOOKI_SERVER = os.environ.get('HTTP_HOST', 'booktype-demo.sourcefabric.org') - DEFAULT_PUBLISHER = "Undefined" - - -class BookiError(Exception): - pass - -# parse JSON - -def parseJSON(js): - try: - return json.loads(js) - except Exception: - return {} - - -def makeTitleUnique(requestedTitle): - """If is unused, return that. Otherwise, - return a title in the form `u'%s - %d' % (requestedTitle, n)` - where n is the lowest non-clashing positive integer. - """ - n = 0 - name = requestedTitle - while True: - titles = models.Book.objects.filter(title=name).count() - urls = models.Book.objects.filter(url_title=booktype_slugify(name)).count() - if not titles and not urls: - return name - n += 1 - name = u'%s - %d' % (requestedTitle, n) - - -def getChaptersFromTOC(toc): - """Convert a nested bookizip TOC structure into a list of tuples - in the form: - - (title, url, is_this_chapter_really_a_booki_section?) - """ - chapters = [] - for elem in toc: - chapters.append((elem.get('title', 'Missing title'), - elem.get('url', 'Missing URL'), - elem.get('type', 'chapter') == 'booki-section')) - if elem.get('children'): - chapters.extend(getChaptersFromTOC(elem['children'])) - - return chapters - - -def importBookFromFile(user, zname, createTOC=False, **extraOptions): - """Create a new book from a bookizip filename""" - - from booki.utils.log import logChapterHistory - - # unzip it - zf = zipfile.ZipFile(zname) - # load info.json - info = json.loads(zf.read('info.json')) - logWarning("Loaded json file %r" % info) - - metadata = info['metadata'] - manifest = info['manifest'] - TOC = info['TOC'] - - if extraOptions.get('book_title', None): - bookTitle = extraOptions['book_title'] - else: - bookTitle = get_metadata(metadata, 'title', ns=DC)[0] - - bookTitle = makeTitleUnique(bookTitle) - logWarning("Chose unique book title %r" % bookTitle) - - if extraOptions.get('book_url', None): - bookURL = extraOptions['book_url'] - else: - bookURL = None - - book = create_book(user, bookTitle, status = "new", bookURL = bookURL) - - if extraOptions.get("hidden"): - book.hidden = True - book.save() - - # this is for Table of Contents - p = re.compile('\ssrc="(.*)"') - - # what if it does not have status "new" - stat = models.BookStatus.objects.filter(book=book, name="new")[0] - - chapters = getChaptersFromTOC(TOC) - n = len(chapters) + 1 #is +1 necessary? - now = datetime.datetime.now() - - for chapterName, chapterFile, is_section in chapters: - urlName = booktype_slugify(chapterName) - - if is_section: # create section - if createTOC: - c = models.BookToc(book = book, - version = book.version, - name = chapterName, - chapter = None, - weight = n, - typeof = 2) - c.save() - n -= 1 - else: # create chapter - # check if i can open this file at all - content = zf.read(chapterFile) - - #content = p.sub(r' src="../\1"', content) - - chapter = models.Chapter(book = book, - version = book.version, - url_title = urlName, - title = chapterName, - status = stat, - content = content, - created = now, - modified = now) - chapter.save() - - history = logChapterHistory(chapter = chapter, - content = content, - user = user, - comment = "", - revision = chapter.revision) - - if createTOC: - c = models.BookToc(book = book, - version = book.version, - name = chapterName, - chapter = chapter, - weight = n, - typeof = 1) - c.save() - n -= 1 - - stat = models.BookStatus.objects.filter(book=book, name="new")[0] - - from django.core.files import File - - for item in manifest.values(): - if item["mimetype"] != 'text/html': - attachmentName = item['url'] - - if attachmentName.startswith("static/"): - att = models.Attachment(book = book, - version = book.version, - status = stat) - - s = zf.read(attachmentName) - f = StringIO(s) - f2 = File(f) - f2.size = len(s) - att.attachment.save(os.path.basename(attachmentName), f2, save=False) - att.save() - f.close() - - # metadata - for namespace in metadata: - # namespace is something like "http://purl.org/dc/elements/1.1/" or "" - # in the former case, preepend it to the name, in {}. - ns = ('{%s}' % namespace if namespace else '') - for keyword, schemes in metadata[namespace].iteritems(): - for scheme, values in schemes.iteritems(): - #schema, if it is set, describes the value's format. - #for example, an identifier might be an ISBN. - sc = ('{%s}' % scheme if scheme else '') - key = "%s%s%s" % (ns, keyword, sc) - for v in values: - if not v: continue - try: - info = models.Info(book=book, name=key) - if len(v) >= 2500: - info.value_text = v - info.kind = 2 - else: - info.value_string = v - info.kind = 0 - info.save() - except: - # For now just ignore any kind of error here. - # Considering we don't handle metadata as we - # should it is not such a problem. - pass - - zf.close() - - return book - - - - - - -def importBookFromURL(user, bookURL, createTOC=False, **extraOptions): - """ - Imports book from the url. Creates project and book for it. - """ - # download it - try: - f = urllib2.urlopen(bookURL) - data = f.read() - f.close() - except urllib2.URLError, e: - logWarning("couldn't read %r: %s" % (bookURL, e)) - logWarning(traceback.format_exc()) - raise - - try: - zf = StringIO(data) - book = importBookFromFile(user, zf, createTOC, **extraOptions) - zf.close() - except Exception, e: - logWarning("couldn't make book from %r: %s" % (bookURL, e)) - logWarning(traceback.format_exc()) - raise - - return book - - -def importBookFromUrl2(user, baseurl, args, **extraOptions): - args['mode'] = 'zip' - url = baseurl + "?" + urlencode(args) - return importBookFromURL(user, url, createTOC=True, **extraOptions) - - - -def expand_macro(chapter): - try: - t = template.loader.get_template_from_string('{% load booktype_tags %} {% booktype_format content %}') - return t.render(Context({"content": chapter})) - except template.TemplateSyntaxError: - return chapter.content - - -def _format_metadata(book): - metadata = {} - # there must be language, creator, identifier and title - #key is [ '{' namespace '}' ] name [ '[' scheme ']' ] - key_re = re.compile(r'^(?:\{([^}]*)\})?' # namespace - r'([^{]+)' # keyword - r'(?:\{([^}]*)\})?$' #schema - ) - - for item in models.Info.objects.filter(book=book): - key = item.name - value = item.getValue() - m = key_re.match(key) - if m is None: - keyword = key - namespace, scheme = '', '' - else: - namespace, keyword, scheme = m.groups('') - add_metadata(metadata, keyword, value, namespace, scheme) - - now = time.strftime("%Y.%m.%d-%H.%M") - created = book.created.strftime("%Y.%m.%d-%H.%M") - mods = models.BookHistory.objects.filter(book=book).datetimes("modified", "day", order='DESC') - if not mods: - lastmod = created - else: - lastmod = mods[0].strftime("%Y.%m.%d-%H.%M") - - author = 'Author' - if book.owner: - if book.owner.first_name: - author = book.owner.first_name - - language = 'en' - if book.language: - language = getattr(book.language, 'abbrevation', 'en') - - # add some default values if values are not otherwise specified - for namespace, keyword, scheme, value in ( - (DC, "publisher", "", DEFAULT_PUBLISHER), - (DC, "language", "", language), - (DC, "creator", "", author), - (DC, "title", "", book.title), - (DC, "date", "start", created), - (DC, "date", "last-modified", lastmod), - (DC, "date", "published", now), - (DC, "identifier", "booki.cc", "http://%s/%s/%s" % (THIS_BOOKI_SERVER, book.url_title, now)) - ): - if not get_metadata(metadata, keyword, namespace, scheme): - add_metadata(metadata, keyword, value, namespace, scheme) - - #XXX add contributors - return metadata - - -def _fix_content(book, chapter, chapter_n): - """fix up the html in various ways""" - content = '
%s
' % (chapter_n, chapter.chapter.content) - - # expand macros - content = expand_macro(chapter.chapter) - - #if isinstance(content, unicode): - # content = content.encode('utf-8') - - tree = html.document_fromstring(content) - - base = "/%s/" % (book.url_title,) - here = base + chapter.chapter.url_title - from os.path import join, normpath - from urlparse import urlsplit, urlunsplit - - def flatten(url, prefix): - scheme, addr, path, query, frag = urlsplit(url) - if scheme: #http, ftp, etc, ... ignore it - return url - path = normpath(join(here, path)) - if not path.startswith(base + prefix): - #What is best here? make an absolute http:// link? - #for now, ignore it. - logWarning("got a wierd link: %r in %s resolves to %r, wanted start of %s" % - (url, here, path, base + prefix)) - return url - path = path[len(base):] - logWarning("turning %r into %r" % (url, path)) - return urlunsplit(('', '', path, query, frag)) - - for e in tree.iter(): - src = e.get('src') - if src is not None: - # src attributes that point to '../static', should point to 'static' - e.set('src', flatten(src, 'static')) - - href = e.get('href') - if href is not None: - e.set('href', flatten(href, '')) - - return etree.tostring(tree, encoding='UTF-8', method='html') - - -def exportBook(book_version): - from booki import bookizip - import time - starttime = time.time() - - (zfile, zname) = tempfile.mkstemp() - - spine = [] - toc_top = [] - toc_current = toc_top - waiting_for_url = [] - - info = { - "version": 1, - "TOC": toc_top, - "spine": spine, - "metadata": _format_metadata(book_version.book), - "manifest": {} - } - - bzip = bookizip.BookiZip(zname, info=info) - chapter_n = 1 - - for i, chapter in enumerate(models.BookToc.objects.filter(version=book_version).order_by("-weight")): - if chapter.chapter: - # It's a real chapter! With content! - try: - content = _fix_content(book_version.book, chapter, chapter_n) - except: - continue - - chapter_n += 1 - ID = "ch%03d_%s" % (i, chapter.chapter.url_title.encode('utf-8')) - filename = ID + '.html' - - toc_current.append({"title": chapter.chapter.title, - "url": filename, - "type": "chapter", - "role": "text" - }) - - # If this is the first chapter in a section, lend our url - # to the section, which has no content and thus no url of - # its own. If this section was preceded by an empty - # section, it will be waiting too, hence "while" rather - # than "if". - while waiting_for_url: - section = waiting_for_url.pop() - section["url"] = filename - - bzip.add_to_package(ID, filename, content, "text/html") - spine.append(ID) - - else: - #A new top level section. - title = chapter.name.encode("utf-8") - ID = "s%03d_%s" % (i, booktype_slugify(unicode(title))) - - toc_current = [] - section = {"title": title, - "url": '', - "type": "booki-section", - "children": toc_current - } - - toc_top.append(section) - waiting_for_url.append(section) - - - #Attachments are images (and perhaps more). They do not know - #whether they are currently in use, or what chapter they belong - #to, so we add them all. - #XXX scan for img links while adding chapters, and only add those. - - for i, attachment in enumerate(models.Attachment.objects.filter(version=book_version)): - try: - f = open(attachment.attachment.name, "rb") - blob = f.read() - f.close() - except (IOError, OSError), e: - msg = "couldn't read attachment %s" % e - logWarning(msg) - continue - - fn = os.path.basename(attachment.attachment.name.encode("utf-8")) - - ID = "att%03d_%s" % (i, booktype_slugify(unicode(fn))) - if '.' in fn: - _, ext = fn.rsplit('.', 1) - mediatype = bookizip.MEDIATYPES.get(ext.lower(), bookizip.MEDIATYPES[None]) - else: - mediatype = bookizip.MEDIATYPES[None] - - bzip.add_to_package(ID, - "static/%s" % fn, - blob, - mediatype) - - - bzip.finish() - logWarning("export took %s seconds" % (time.time() - starttime)) - return zname diff --git a/lib/booki/editor/management/commands/bookexport.py b/lib/booki/editor/management/commands/bookexport.py deleted file mode 100644 index 65bb011c7..000000000 --- a/lib/booki/editor/management/commands/bookexport.py +++ /dev/null @@ -1,79 +0,0 @@ -# This file is part of Booktype. -# Copyright (c) 2012 Aleksandar Erkalovic -# -# Booktype is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Booktype is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Booktype. If not, see . - -from django.core.management.base import BaseCommand, CommandError -from optparse import make_option -from booki.editor import common, models - -import shutil - -class Command(BaseCommand): - help = "Export book content as a ZIP file. For now, only the content of one book version will be exported and you will not get the history data." - args = "" - - option_list = BaseCommand.option_list + ( - make_option('--book-version', - action='store', - dest='book_version', - default=None, - help='Book version, e.g. "1.0".'), - - make_option('--output', - action='store', - dest='output_name', - default=None, - help='Output filename or -- for STDOUT, e.g. "my_book.zip".'), - ) - - requires_model_validation = False - - def handle(self, *args, **options): - - if len(args) == 0: - raise CommandError("You must specify book name!") - - try: - book = models.Book.objects.get(url_title__iexact=args[0]) - except models.Book.DoesNotExist: - raise CommandError('Book "%s" does not exist.' % args[0]) - - book_version = book.getVersion(options['book_version']) - - if not book_version: - raise CommandError('Book version %s does not exist.' % options['book_version']) - - fileName = common.exportBook(book_version) - - exportFileName = None - - if options['output_name']: - if options['output_name'] == '--': - print open(fileName,'rb').read(), - - import os - os.unlink(fileName) - return - else: - exportFileName = options['output_name'] - else: - exportFileName = 'export-%s.zip' % book.url_title - - shutil.move(fileName, exportFileName) - - if options['verbosity'] in ['1', '2']: - print 'Book successfully exported into "%s" file.' % exportFileName - - diff --git a/lib/booki/editor/management/commands/bookrename.py b/lib/booki/editor/management/commands/bookrename.py index 5e8b2e514..53a0aa55b 100644 --- a/lib/booki/editor/management/commands/bookrename.py +++ b/lib/booki/editor/management/commands/bookrename.py @@ -15,52 +15,52 @@ # along with Booktype. If not, see . from django.core.management.base import BaseCommand, CommandError -from optparse import make_option from django.contrib.auth.models import User from booki.editor import models +from booktype.utils.book import rename_book + class Command(BaseCommand): - args = "" help = "Rename book." + requires_model_validation = False + BOOK_NAME = '' - option_list = BaseCommand.option_list + ( - make_option('--owner', - action='store', - dest='owner', - default=None, - help='Set new owner of the book.'), - - make_option('--new-book-title', - action='store', - dest='new_book_title', - default=None, - help='Set new book title.'), + def add_arguments(self, parser): + parser.add_argument(self.BOOK_NAME, nargs=1, type=str) + parser.add_argument('--owner', + action='store', + dest='owner', + default=None, + help='Set new owner of the book.') - make_option('--new-book-url', - action='store', - dest='new_book_url', - default=None, - help='Set new book url name.'), + parser.add_argument('--new-book-title', + action='store', + dest='new_book_title', + default=None, + help='Set new book title.') - ) - - requires_model_validation = False + parser.add_argument('--new-book-url', + action='store', + dest='new_book_url', + default=None, + help='Set new book url name.') def handle(self, *args, **options): - if len(args) != 1: + book_name = options[self.BOOK_NAME][0] + + if not book_name: raise CommandError("You must specify book name.") try: - book = models.Book.objects.get(url_title__iexact=args[0]) + book = models.Book.objects.get(url_title__iexact=book_name) except models.Book.DoesNotExist: - raise CommandError('Book "%s" does not exist.' % args[0]) + raise CommandError('Book "%s" does not exist.' % book_name) if options['new_book_title']: book.title = options['new_book_title'] if options['new_book_url']: - from booktype.utils.book import rename_book rename_book(book, book.title, options['new_book_url']) if options['owner']: diff --git a/lib/booki/editor/management/commands/brokenlinks.py b/lib/booki/editor/management/commands/brokenlinks.py index a1c3ae106..94ea738a0 100644 --- a/lib/booki/editor/management/commands/brokenlinks.py +++ b/lib/booki/editor/management/commands/brokenlinks.py @@ -14,19 +14,17 @@ # You should have received a copy of the GNU Affero General Public License # along with Booktype. If not, see . -from django.core.management.base import BaseCommand, CommandError -from optparse import make_option -import os.path +import os import urllib2 - -from booki.editor import models from lxml import etree, html from django.test import Client - +from django.core.management.base import BaseCommand, CommandError +from booki.editor import models cacheLinks = {} + class HeadRequest(urllib2.Request): def get_method(self): return "HEAD" @@ -59,13 +57,14 @@ def checkLink(options, chapter, urlLink): if not options['no_cache']: cacheLinks[urlLink] = returnCode - + print ' [%s]' % returnCode else: if options['no_local']: return c = Client() - newUrl = os.path.normpath('/%s/_v/%s/%s/%s' % (chapter.version.book.url_title, chapter.version.getVersion(), chapter.url_title, urlLink)) + newUrl = os.path.normpath('/%s/_v/%s/%s/%s' % ( + chapter.version.book.url_title, chapter.version.getVersion(), chapter.url_title, urlLink)) print ' >> ', newUrl, response = c.get(newUrl) @@ -74,60 +73,61 @@ def checkLink(options, chapter, urlLink): class Command(BaseCommand): - args = ' [, , ...]' + BOOK_NAMES = ' [, , ...]' help = 'Check links in books.' - option_list = BaseCommand.option_list + ( - make_option('--no-remote', - action='store_true', - dest='no_remote', - default=False, - help='Do we check for remote links?'), - - make_option('--no-local', - action='store_true', - dest='no_local', - default=False, - help='Do we check for local links?'), - - make_option('--no-cache', - action='store_true', - dest='no_cache', - default=False, - help='Do not cache network links.'), - - make_option('--ignore-url', - action='append', - dest='ignore_url', - default=[], - help='What hosts to ignore, e.g. http://www.wikipedia.org/'), - - ) - + def add_arguments(self, parser): + parser.add_argument(self.BOOK_NAMES, nargs='+', type=str) + + parser.add_argument('--no-remote', + action='store_true', + dest='no_remote', + default=False, + help='Do we check for remote links?') + + parser.add_argument('--no-local', + action='store_true', + dest='no_local', + default=False, + help='Do we check for local links?') + + parser.add_argument('--no-cache', + action='store_true', + dest='no_cache', + default=False, + help='Do not cache network links.') + + parser.add_argument('--ignore-url', + action='append', + dest='ignore_url', + default=[], + help='What hosts to ignore, e.g. http://www.wikipedia.org/') + def handle(self, *args, **options): global cacheLinks + book_names = options.get(self.BOOK_NAMES, []) + # filter only books we want - if len(args) > 0: - booksList = models.Book.objects.filter(url_title__in=args).order_by('url_title') + if book_names: + booksList = models.Book.objects.filter(url_title__in=book_names).order_by('url_title') else: booksList = models.Book.objects.all().order_by('url_title') - for book in booksList: print '[%s]' % book.url_title try: for chapter in models.Chapter.objects.filter(version__book=book): print ' [%s]' % chapter.url_title, - + try: tree = html.document_fromstring(chapter.content) print '' except: print ' [ERROR PARSING HTML]' continue - + for elem in tree.iter(): src = elem.get('src') if src: diff --git a/lib/booki/editor/management/commands/confdel.py b/lib/booki/editor/management/commands/confdel.py index ed7fb8172..8c4c43197 100644 --- a/lib/booki/editor/management/commands/confdel.py +++ b/lib/booki/editor/management/commands/confdel.py @@ -15,27 +15,28 @@ # along with Booktype. If not, see . from django.core.management.base import BaseCommand, CommandError -from optparse import make_option from django.conf import settings from booktype.utils import config class Command(BaseCommand): - args = "" help = "Delete configuration variable." requires_model_validation = False + def add_arguments(self, parser): + parser.add_argument("", nargs=1, type=str) + def handle(self, *args, **options): if not hasattr(settings, 'BOOKTYPE_CONFIG'): raise CommandError('Does not have BOOKTYPE_CONFIG in settings.py file.') - if len(args) != 1: + if not options[''][0]: raise CommandError("You must specify variable name") - if not settings.BOOKTYPE_CONFIG.has_key(args[0]): + if not settings.BOOKTYPE_CONFIG.has_key(options[''][0]): raise CommandError("There is no such variable.") - del settings.BOOKTYPE_CONFIG[args[0]] + del settings.BOOKTYPE_CONFIG[options[''][0]] config.save_configuration() diff --git a/lib/booki/editor/management/commands/confget.py b/lib/booki/editor/management/commands/confget.py index 26dfa6f1e..ec34f8279 100644 --- a/lib/booki/editor/management/commands/confget.py +++ b/lib/booki/editor/management/commands/confget.py @@ -14,41 +14,37 @@ # You should have received a copy of the GNU Affero General Public License # along with Booktype. If not, see . -from django.core.management.base import BaseCommand, CommandError -from optparse import make_option +import json +from django.core.management.base import BaseCommand, CommandError from django.conf import settings -from booktype.utils import config -import json class Command(BaseCommand): - args = "" help = "Get value for configuration variable." - - option_list = BaseCommand.option_list + ( - make_option('--as_json', - action='store_true', - dest='as_json', - default=False, - help='Value is defined as JSON encoded string.'), - ) - requires_model_validation = False + def add_arguments(self, parser): + parser.add_argument("", nargs=1, type=str) + parser.add_argument('--as_json', + action='store_true', + dest='as_json', + default=False, + help='Value is defined as JSON encoded string.') + def handle(self, *args, **options): if not hasattr(settings, 'BOOKTYPE_CONFIG'): raise CommandError('Does not have BOOKTYPE_CONFIG in settings.py file.') - if len(args) != 1: + if not options[""][0]: raise CommandError("You must specify variable name") - if not settings.BOOKTYPE_CONFIG.has_key(args[0]): + if not settings.BOOKTYPE_CONFIG.has_key(options[""][0]): raise CommandError("There is no such variable.") - value = settings.BOOKTYPE_CONFIG[args[0]] + value = settings.BOOKTYPE_CONFIG[options[""][0]] if options['as_json']: value = json.dumps(value) - self.stdout.write(str(value)+"\n") + self.stdout.write(str(value) + "\n") diff --git a/lib/booki/editor/management/commands/conflist.py b/lib/booki/editor/management/commands/conflist.py index 586895493..fa1a99845 100644 --- a/lib/booki/editor/management/commands/conflist.py +++ b/lib/booki/editor/management/commands/conflist.py @@ -14,25 +14,22 @@ # You should have received a copy of the GNU Affero General Public License # along with Booktype. If not, see . -from django.core.management.base import BaseCommand, CommandError -from optparse import make_option +from django.core.management.base import BaseCommand from django.conf import settings + class Command(BaseCommand): - args = "" help = "List all user defined configuration variables." - - option_list = BaseCommand.option_list + ( - make_option('--values', - action='store_true', - dest='values', - default=False, - help='Show variable values.'), - ) - requires_model_validation = False + def add_arguments(self, parser): + parser.add_argument('--values', + action='store_true', + dest='values', + default=False, + help='Show variable values.') + def handle(self, *args, **options): if not hasattr(settings, 'BOOKTYPE_CONFIG'): self.stderr.write('Does not have BOOKTYPE_CONFIG in settings.py file.') @@ -55,6 +52,5 @@ def handle(self, *args, **options): s += str(value) s += "\n" - - self.stdout.write(s) + self.stdout.write(s) diff --git a/lib/booki/editor/management/commands/confset.py b/lib/booki/editor/management/commands/confset.py index efb9883dd..6d15bc4f0 100644 --- a/lib/booki/editor/management/commands/confset.py +++ b/lib/booki/editor/management/commands/confset.py @@ -14,49 +14,44 @@ # You should have received a copy of the GNU Affero General Public License # along with Booktype. If not, see . -from django.core.management.base import BaseCommand, CommandError -from optparse import make_option +import json +from django.core.management.base import BaseCommand, CommandError from django.conf import settings from booktype.utils import config -import json + class Command(BaseCommand): - args = " " help = "Set value for configuration variable." - option_list = BaseCommand.option_list + ( - make_option('--as_json', - action='store_true', - dest='as_json', - default=False, - help='Value is defined as JSON encoded string.'), - - make_option('--integer', - action='store_true', - dest='integer', - default=False, - help='Value is a integer.'), - - make_option('--float', - action='store_true', - dest='float', - default=False, - help='Value is a float.'), - - make_option('--append', - action='store_true', - dest='append', - default=False, - help='Append value to the end of list.'), - - make_option('--remove', - action='store_true', - dest='remove', - default=False, - help='Remove value from the list.'), - ) + def add_arguments(self, parser): + parser.add_argument(" ", nargs=2, type=str) + parser.add_argument('--as_json', + action='store_true', + dest='as_json', + default=False, + help='Value is defined as JSON encoded string.') + parser.add_argument('--integer', + action='store_true', + dest='integer', + default=False, + help='Value is a integer.') + parser.add_argument('--float', + action='store_true', + dest='float', + default=False, + help='Value is a float.') + parser.add_argument('--append', + action='store_true', + dest='append', + default=False, + help='Append value to the end of list.') + parser.add_argument('--remove', + action='store_true', + dest='remove', + default=False, + help='Remove value from the list.') requires_model_validation = False @@ -64,11 +59,11 @@ def handle(self, *args, **options): if not hasattr(settings, 'BOOKTYPE_CONFIG'): raise CommandError('Does not have BOOKTYPE_CONFIG in settings.py file.') - if len(args) != 2: + if len(options[' ']) != 2: raise CommandError("You must specify variable name and value.") - key = args[0] - value = args[1] + key = options[' '][0] + value = options[' '][1] if options['integer']: try: @@ -81,7 +76,6 @@ def handle(self, *args, **options): value = float(value) except ValueError: raise CommandError("I don't think this %s is a number!" % value) - if options['as_json']: try: @@ -112,9 +106,8 @@ def handle(self, *args, **options): raise CommandError("Can not append to something that is not a list") else: config.set_configuration(key, value) - + try: config.save_configuration() except config.ConfigurationError: raise CommandError("Could not save the file.") - diff --git a/lib/booki/editor/tests.py b/lib/booki/editor/tests.py index 10f04760a..081694466 100644 --- a/lib/booki/editor/tests.py +++ b/lib/booki/editor/tests.py @@ -31,7 +31,11 @@ def test_command_error_raised_owner_does_not_exist(self): def test_save_new_book_title(self): user = User.objects.create_user('rochard') - book = Book.objects.create(title='The Island of Truth', owner=user) + book = Book.objects.create( + title='The Island of Truth', + owner=user, + url_title='the_island_of_truth' + ) call_command( 'bookrename', @@ -44,7 +48,11 @@ def test_save_new_book_title(self): def test_save_new_book_url(self): user = User.objects.create_user('rochard') - book = Book.objects.create(title='The Island of Truth', owner=user) + book = Book.objects.create( + title='The Island of Truth', + owner=user, + url_title='the_island_of_truth' + ) call_command( 'bookrename', diff --git a/lib/booki/messaging/templatetags/messaging_tags.py b/lib/booki/messaging/templatetags/messaging_tags.py index f60105121..9df34e642 100644 --- a/lib/booki/messaging/templatetags/messaging_tags.py +++ b/lib/booki/messaging/templatetags/messaging_tags.py @@ -19,11 +19,9 @@ from django import template from django.conf import settings -from django.template.context import Context -from django.contrib.auth import models as auth_models from booki.messaging.views import get_endpoint_or_none -from booki.messaging.models import Post, PostAppearance, Endpoint, Following +from booki.messaging.models import Post, PostAppearance, Following register = template.Library() @@ -117,7 +115,7 @@ def user_followingbox(username, template_name="messaging/followingbox.html"): followings = Following.objects.filter(follower=user) target_users = (following.target.syntax[1:] for following in followings if following.target.syntax.startswith("@")) t = template.loader.get_template(template_name) - return t.render(Context(dict(target_users=target_users))) + return t.render(dict(target_users=target_users)) @register.simple_tag def user_followersbox(username, template_name="messaging/followersbox.html"): @@ -125,7 +123,7 @@ def user_followersbox(username, template_name="messaging/followersbox.html"): followings = Following.objects.filter(target=endpoint) followers = (following.follower.syntax[1:] for following in followings) t = template.loader.get_template(template_name) - return t.render(Context(dict(followers=followers))) + return t.render(dict(followers=followers)) @register.inclusion_tag("messaging/tags.html") def user_tagbox(username): diff --git a/lib/booktype/api/management/commands/generate_filters.py b/lib/booktype/api/management/commands/generate_filters.py index e2f7c5915..a5b9fe931 100644 --- a/lib/booktype/api/management/commands/generate_filters.py +++ b/lib/booktype/api/management/commands/generate_filters.py @@ -79,15 +79,17 @@ class Command(BaseCommand): Fast made command for automation generating django-filters class for provided app.Model """ - args = '' help = 'Generate filters class by provided app.Model' + def add_arguments(self, parser): + parser.add_argument('', nargs=1, type=str) + def handle(self, *args, **options): - if not args: + if not options[''][0]: raise CommandError('Enter app.Model') - app_name = args[0].split('.')[0] - model_name = args[0].split('.')[1] + app_name = options[''][0].split('.')[0] + model_name = options[''][0].split('.')[1] model = apps.get_model(app_label=app_name, model_name=model_name) diff --git a/lib/booktype/apps/account/views.py b/lib/booktype/apps/account/views.py index 613feba91..6769e6757 100644 --- a/lib/booktype/apps/account/views.py +++ b/lib/booktype/apps/account/views.py @@ -377,7 +377,7 @@ def post(self, request, *args, **kwargs): messages.success( self.request, _('Password changed successfully!')) - return redirect(self.get_success_url()) + return redirect(reverse('accounts:signin')) else: self.object = self.get_object() form_class = self.get_form_class() diff --git a/lib/booktype/apps/convert/tasks.py b/lib/booktype/apps/convert/tasks.py index 2714a9bf1..bc5d6fd2b 100644 --- a/lib/booktype/apps/convert/tasks.py +++ b/lib/booktype/apps/convert/tasks.py @@ -21,6 +21,7 @@ from uuid import uuid4 from celery import group +from celery.result import allow_join_result from booktype.convert import loader from booktype.convert.runner import run_conversion @@ -93,6 +94,8 @@ def convert(request_data, base_path): } """ + # TODO we should use a chain of tasks + assets = AssetCollection(base_path) assets.add_urls(request_data.assets) @@ -118,9 +121,13 @@ def convert(request_data, base_path): ) subtasks.append(subtask) - job = group(subtasks) + job = group(subtasks, disable_sync_subtasks=False) result = job.apply_async() - result.join(propagate=False) + + # TODO we should use chain here + # http://docs.celeryq.org/en/latest/userguide/tasks.html#task-synchronous-subtasks + with allow_join_result(): + result.join(propagate=False) subtasks_info = {async.task_id: async for async in result.children} celery.current_task.update_state(state="PROGRESS", meta=subtasks_info) diff --git a/lib/booktype/apps/core/management/commands/createuser.py b/lib/booktype/apps/core/management/commands/createuser.py index 3dbb5ea40..3aa085ac2 100644 --- a/lib/booktype/apps/core/management/commands/createuser.py +++ b/lib/booktype/apps/core/management/commands/createuser.py @@ -15,32 +15,24 @@ # along with Booktype. If not, see . from django.core.management.base import BaseCommand, CommandError -from optparse import make_option from django.db.utils import Error - from django.contrib.auth.models import User class Command(BaseCommand): help = "Used to create a user" - option_list = BaseCommand.option_list + ( - make_option('--username', dest='username', default=None, - help='Specifies the username for the superuser.'), - - make_option('--email', dest='email', default=None, - help='Specifies the email address for the user.'), - - make_option('--fullname', dest='fullname', default=None, - help='Specifies the fullname for the user.'), - - make_option('--password', dest='password', default=None, - help='Specifies the password address for the user.'), - - make_option('--is-superuser', action='store_true', dest='is_superuser', - default=False, help='User has superpowers.'), - - ) + def add_arguments(self, parser): + parser.add_argument('--username', dest='username', default=None, + help='Specifies the username for the superuser.') + parser.add_argument('--email', dest='email', default=None, + help='Specifies the email address for the user.') + parser.add_argument('--fullname', dest='fullname', default=None, + help='Specifies the fullname for the user.') + parser.add_argument('--password', dest='password', default=None, + help='Specifies the password address for the user.') + parser.add_argument('--is-superuser', action='store_true', dest='is_superuser', + default=False, help='User has superpowers.') requires_model_validation = False diff --git a/lib/booktype/apps/core/management/commands/update_permissions.py b/lib/booktype/apps/core/management/commands/update_permissions.py index 1d44e45a9..ee978c4f6 100644 --- a/lib/booktype/apps/core/management/commands/update_permissions.py +++ b/lib/booktype/apps/core/management/commands/update_permissions.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from optparse import make_option from django.conf import settings from django.core.management.base import BaseCommand @@ -18,12 +17,9 @@ class Command(BaseCommand): # TODO: load permissions just for one app specified as param - option_list = BaseCommand.option_list + ( - make_option( - '--delete-orphans', action='store_true', - help='Specify this param to delete undeclared permissions' - ), - ) + def add_arguments(self, parser): + parser.add_argument('--delete-orphans', action='store_true', + help='Specify this param to delete undeclared permissions') def handle(self, *args, **options): saved_perms = [] diff --git a/lib/booktype/apps/core/templates/core/booktype_groups.html b/lib/booktype/apps/core/templates/core/booktype_groups.html deleted file mode 100644 index 5aea2cee7..000000000 --- a/lib/booktype/apps/core/templates/core/booktype_groups.html +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/lib/booktype/apps/core/templatetags/booktype_tags.py b/lib/booktype/apps/core/templatetags/booktype_tags.py index f9c6d9f81..d0776a083 100644 --- a/lib/booktype/apps/core/templatetags/booktype_tags.py +++ b/lib/booktype/apps/core/templatetags/booktype_tags.py @@ -55,10 +55,10 @@ class FormatGroupsNode(template.Node): def render(self, context): t = template.loader.get_template('core/booktype_groups.html') - return t.render(Context({ + return t.render({ 'groups': models.BookiGroup.objects.all().order_by("name"), - 'books': models.Book.objects.filter( - hidden=False)}, autoescape=context.autoescape)) + 'books': models.Book.objects.filter(hidden=False) + }) @register.tag(name="booktype_groups") @@ -125,13 +125,11 @@ def render(self, context): {%% booktype_%s book args %%}' t = template.loader.get_template_from_string( tag_text % (mtch.group(1).lower(),)) - con = t.render( - Context({ - "content": chapter, - "book": chapter.version.book, - "args": mtch.group(2) - }) - ) + con = t.render({ + "content": chapter, + "book": chapter.version.book, + "args": mtch.group(2) + }) except template.TemplateSyntaxError: con = '\ @@ -215,12 +213,10 @@ def render(self, context): copyright_description = self.args.resolve(context) or '' - return t.render( - Context({ - 'chapters': chapters, - "copyright": copyright_description[1:-1] - }, autoescape=context.autoescape) - ) + return t.render({ + 'chapters': chapters, + "copyright": copyright_description[1:-1] + }) @register.tag(name="booktype_authors") diff --git a/lib/booktype/apps/importer/management/__init__.py b/lib/booktype/apps/importer/management/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/booktype/apps/importer/management/commands/__init__.py b/lib/booktype/apps/importer/management/commands/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/booktype/apps/importer/management/commands/bookimport.py b/lib/booktype/apps/importer/management/commands/bookimport.py deleted file mode 100644 index fb3fd70c7..000000000 --- a/lib/booktype/apps/importer/management/commands/bookimport.py +++ /dev/null @@ -1,76 +0,0 @@ -# This file is part of Booktype. -# Copyright (c) 2013 Aleksandar Erkalovic -# -# Booktype is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Booktype is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Booktype. If not, see . - -from django.core.management.base import BaseCommand, CommandError -from optparse import make_option -from django.contrib.auth.models import User - -from booktype.utils.misc import import_book_from_file - -class Command(BaseCommand): - args = " [, , ...]" - help = "Imports one or many books from epub files." - - option_list = BaseCommand.option_list + ( - make_option('--owner', - action='store', - dest='owner', - default='booki', - help='Who is owner of the imported book.'), - - make_option('--new-book-title', - action='store', - dest='new_book_title', - default=None, - help='Title of the new book.'), - - make_option('--new-book-url', - action='store', - dest='new_book_url', - default=None, - help='URL name of the new book.'), - - ) - - requires_model_validation = False - - def handle(self, *args, **options): - if len(args) == 0: - raise CommandError("You must specify at least one booki-zip filename.") - - try: - user = User.objects.get(username=options['owner']) - except User.DoesNotExist: - raise CommandError('User "%s" does not exist. Can not finish import.' % options['owner']) - - for fileName in args: - try: - extraOptions = {} - - if options['new_book_title']: - extraOptions['book_title'] = options['new_book_title'] - - if options['new_book_url']: - extraOptions['book_url'] = options['new_book_url'] - - import_book_from_file(fileName, user, **extraOptions) - except IOError: - raise CommandError('File "%s" does not exist. Can not finish import.' % fileName) - else: - if options['verbosity'] in ['1', '2']: - print 'EPUB "%s" file successfully imported.' % fileName - - diff --git a/lib/booktype/apps/loadsave/management/commands/bookload.py b/lib/booktype/apps/loadsave/management/commands/bookload.py index e0baf26ea..3b53554dc 100644 --- a/lib/booktype/apps/loadsave/management/commands/bookload.py +++ b/lib/booktype/apps/loadsave/management/commands/bookload.py @@ -15,48 +15,40 @@ # along with Booktype. If not, see . from django.core.management.base import BaseCommand, CommandError -from optparse import make_option from django.contrib.auth.models import User from booktype.utils.misc import import_book_from_file + class Command(BaseCommand): - args = " [, , ...]" help = "Imports one or many books from epub files." - - option_list = BaseCommand.option_list + ( - make_option('--owner', - action='store', - dest='owner', - default='booki', - help='Who is owner of the imported book.'), - - make_option('--new-book-title', - action='store', - dest='new_book_title', - default=None, - help='Title of the new book.'), - - make_option('--new-book-url', - action='store', - dest='new_book_url', - default=None, - help='URL name of the new book.'), - - ) - requires_model_validation = False - def handle(self, *args, **options): - if len(args) == 0: - raise CommandError("You must specify at least one booki-zip filename.") + def add_arguments(self, parser): + parser.add_argument(" [, , ...]", nargs='+', type=str) + parser.add_argument('--owner', + action='store', + dest='owner', + default='booki', + help='Who is owner of the imported book.') + parser.add_argument('--new-book-title', + action='store', + dest='new_book_title', + default=None, + help='Title of the new book.') + parser.add_argument('--new-book-url', + action='store', + dest='new_book_url', + default=None, + help='URL name of the new book.') + def handle(self, *args, **options): try: user = User.objects.get(username=options['owner']) except User.DoesNotExist: raise CommandError('User "%s" does not exist. Can not finish import.' % options['owner']) - for fileName in args: + for fileName in options[' [, , ...]']: try: extraOptions = {} diff --git a/lib/booktype/apps/loadsave/management/commands/booksave.py b/lib/booktype/apps/loadsave/management/commands/booksave.py index 83a5d2170..72ceb4a12 100644 --- a/lib/booktype/apps/loadsave/management/commands/booksave.py +++ b/lib/booktype/apps/loadsave/management/commands/booksave.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU Affero General Public License # along with Booktype. If not, see . -from optparse import make_option from django.core.management.base import BaseCommand, CommandError from booki.editor import models @@ -22,37 +21,28 @@ class Command(BaseCommand): help = "Export book content as EPUB file." - args = "" - - option_list = BaseCommand.option_list + ( - make_option( - '--book-version', - action='store', - dest='book_version', - default=None, - help='Book version, e.g.' - ), - - make_option( - '--output', - action='store', - dest='output_name', - default=None, - help='Output filename or -- for STDOUT, e.g. my_book.zip.' - ), - ) - requires_model_validation = False - def handle(self, *args, **options): + def add_arguments(self, parser): + parser.add_argument('', nargs=1, type=str) + parser.add_argument('--book-version', + action='store', + dest='book_version', + default=None, + help='Book version, e.g.') + parser.add_argument('--output', + action='store', + dest='output_name', + default=None, + help='Output filename or -- for STDOUT, e.g. my_book.epub.') - if len(args) == 0: - raise CommandError("You must specify book name!") + def handle(self, *args, **options): + book_name = options[''][0] try: - book = models.Book.objects.get(url_title__iexact=args[0]) + book = models.Book.objects.get(url_title__iexact=book_name) except models.Book.DoesNotExist: - raise CommandError('Book "%s" does not exist.' % args[0]) + raise CommandError('Book "%s" does not exist.' % book_name) book_version = book.get_version(options['book_version']) diff --git a/lib/booktype/apps/reports/management/commands/report.py b/lib/booktype/apps/reports/management/commands/report.py index 56f10fbb9..77322a8d1 100644 --- a/lib/booktype/apps/reports/management/commands/report.py +++ b/lib/booktype/apps/reports/management/commands/report.py @@ -17,11 +17,10 @@ import os.path import datetime import StringIO -from optparse import make_option +from email.MIMEImage import MIMEImage from django import template from django.conf import settings -from django.template.context import Context from django.core.management.base import BaseCommand from django.core.mail import EmailMultiAlternatives from django.db.models import Count @@ -61,6 +60,7 @@ def get_history(): return history + def get_chart(): hours = [0] * 24 max_num = 0 @@ -89,19 +89,19 @@ def get_chart(): class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('--send-email', - action='store_true', - dest='send_email', - default=False, - help='Send email to admin'), - - make_option('--days', - action='store', - dest='days', - default=0, - help='N days ago') - ) + + def add_arguments(self, parser): + parser.add_argument('--send-email', + action='store_true', + dest='send_email', + default=False, + help='Send email to admin') + + parser.add_argument('--days', + action='store', + dest='days', + default=0, + help='N days ago') def handle(self, *args, **options): global now @@ -120,8 +120,8 @@ def handle(self, *args, **options): # get groups created today groups = BookiGroup.objects.filter(created__year=now.year, - created__month=now.month, - created__day=now.day) + created__month=now.month, + created__day=now.day) history = get_history() info = get_info() @@ -135,15 +135,15 @@ def handle(self, *args, **options): # render result t = template.loader.get_template('reports/booktype_daily_report.html') - con = t.render(Context({"users": users, - "books": books, - "groups": groups, - "history": history, - "report_date": now, - "info": info, - "booktype_name": BOOKTYPE_NAME, - "site_url": settings.BOOKTYPE_URL - })) + con = t.render({"users": users, + "books": books, + "groups": groups, + "history": history, + "report_date": now, + "info": info, + "booktype_name": BOOKTYPE_NAME, + "site_url": settings.BOOKTYPE_URL + }) if options['send_email']: reports_email_from = config.get_configuration('REPORTS_EMAIL_FROM') @@ -194,8 +194,6 @@ def handle(self, *args, **options): data = output.getvalue() - from email.MIMEImage import MIMEImage - msgImage = MIMEImage(data) msgImage.add_header('Content-ID', '') msg.attach(msgImage) diff --git a/lib/booktype/apps/reports/management/commands/reportweekly.py b/lib/booktype/apps/reports/management/commands/reportweekly.py index 5f5249fe5..471719745 100644 --- a/lib/booktype/apps/reports/management/commands/reportweekly.py +++ b/lib/booktype/apps/reports/management/commands/reportweekly.py @@ -17,11 +17,10 @@ import StringIO import os.path import datetime -from optparse import make_option +from email.MIMEImage import MIMEImage from django import template from django.conf import settings -from django.template.context import Context from django.core.management.base import BaseCommand from django.core.mail import EmailMultiAlternatives from django.db.models import Count @@ -55,6 +54,7 @@ def get_history(): return history + def get_chart(): hours = [] max_num = 0 @@ -88,19 +88,18 @@ def get_chart(): class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('--send-email', - action='store_true', - dest='send_email', - default=False, - help='Send email to admin'), - - make_option('--days', - action='store', - dest='days', - default=0, - help='N days ago') - ) + + def add_arguments(self, parser): + parser.add_argument('--send-email', + action='store_true', + dest='send_email', + default=False, + help='Send email to admin') + parser.add_argument('--days', + action='store', + dest='days', + default=0, + help='N days ago') def handle(self, *args, **options): global now @@ -137,19 +136,19 @@ def handle(self, *args, **options): Count('user')).order_by("-user__count")[:10]] t = template.loader.get_template('reports/booktype_weekly_report.html') - con = t.render(Context({"users": users, - "books": books, - "groups": groups, - "history": history, - "report_date": now, - "info": info, - "booktype_name": BOOKTYPE_NAME, - "week_ago": week_ago, - "now_date": now, - "active_books": active_books, - "active_users": active_users, - "site_url": settings.BOOKTYPE_URL - })) + con = t.render({"users": users, + "books": books, + "groups": groups, + "history": history, + "report_date": now, + "info": info, + "booktype_name": BOOKTYPE_NAME, + "week_ago": week_ago, + "now_date": now, + "active_books": active_books, + "active_users": active_users, + "site_url": settings.BOOKTYPE_URL + }) if options['send_email']: reports_email_from = config.get_configuration('REPORTS_EMAIL_FROM') @@ -170,7 +169,6 @@ def handle(self, *args, **options): if config.get_configuration('REPORTS_CUSTOM_FONT_PATH'): font_path = config.get_configuration('REPORTS_CUSTOM_FONT_PATH') - font = ImageFont.truetype(font_path, 12) text_size = font.getsize("P") @@ -215,8 +213,6 @@ def _day(n): image.save(output, 'PNG') data = output.getvalue() - from email.MIMEImage import MIMEImage - msgImage = MIMEImage(data) msgImage.add_header('Content-ID', '') msg.attach(msgImage) diff --git a/lib/booktype/apps/reports/management/commands/signups.py b/lib/booktype/apps/reports/management/commands/signups.py index 5b461e15b..d392a983d 100644 --- a/lib/booktype/apps/reports/management/commands/signups.py +++ b/lib/booktype/apps/reports/management/commands/signups.py @@ -1,6 +1,6 @@ import datetime -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from django.contrib.auth.models import User diff --git a/lib/booktype/apps/themes/management/commands/reset_theme.py b/lib/booktype/apps/themes/management/commands/reset_theme.py index d7ca8cde9..6af6f4785 100644 --- a/lib/booktype/apps/themes/management/commands/reset_theme.py +++ b/lib/booktype/apps/themes/management/commands/reset_theme.py @@ -1,35 +1,29 @@ # -*- coding: utf-8 -*- import os -import booktype - -from optparse import make_option +from distutils.dir_util import copy_tree +import booktype from django.utils.six.moves import input from django.core.management.base import BaseCommand, CommandError +from django.conf import settings class Command(BaseCommand): help = 'Copy the files for a specified theme from skeleton to a booktype instance' - args = '' - option_list = BaseCommand.option_list + ( - make_option( - '--noinput', - action='store_false', - dest='interactive', - default=True, - help='Tells Command to NOT prompt the user for input of any kind' - ), - ) + def add_arguments(self, parser): + parser.add_argument('', nargs=1, type=str) + parser.add_argument('--noinput', + action='store_false', + dest='interactive', + default=True, + help='Tells Command to NOT prompt the user for input of any kind') def __init__(self): super(Command, self).__init__() self.booktype_source = os.path.dirname(booktype.__file__) def handle(self, *args, **options): - if len(args) == 0: - raise CommandError('You must provide a theme name as argument. E.g: ./manage.py reset_theme academic') - if options.get('interactive'): msg = ( "\nThis will reset your theme files with the ones coming from Booktype" @@ -43,12 +37,12 @@ def handle(self, *args, **options): confirm = input('Please enter either "yes" or "no": ') continue if confirm == 'yes': - self.reset_themes(args) + self.reset_themes(options['']) else: self.stdout.write('No action taken. Good bye!') break else: - self.reset_themes(args) + self.reset_themes(options['']) def theme_path(self, theme_name): return '{}/skeleton/themes/{}'.format(self.booktype_source, theme_name) @@ -64,8 +58,6 @@ def reset_themes(self, args): self.stdout.write('Theme with name `%s` does not exist.' % theme_name) def copy_theme(self, theme_name): - from distutils.dir_util import copy_tree - from django.conf import settings theme_path = self.theme_path(theme_name) destination = '{}/themes/{}'.format(settings.BOOKTYPE_ROOT, theme_name) diff --git a/lib/booktype/apps/themes/templatetags/themes.py b/lib/booktype/apps/themes/templatetags/themes.py index c86bb6b90..107c95bfd 100644 --- a/lib/booktype/apps/themes/templatetags/themes.py +++ b/lib/booktype/apps/themes/templatetags/themes.py @@ -2,7 +2,6 @@ from django import template from django.template.base import Template -from django.template.context import Context from django.conf import settings from ..utils import read_theme_info @@ -37,9 +36,7 @@ def list_theme_options(context): f.close() t = Template(unicode(s, 'utf8')) - c = Context(context) - - content = t.render(c) + content = t.render(context) options.append({'name': theme, 'content': content}) diff --git a/lib/booktype/convert/epub/converter.py b/lib/booktype/convert/epub/converter.py index a8fa5893f..448d19cfd 100644 --- a/lib/booktype/convert/epub/converter.py +++ b/lib/booktype/convert/epub/converter.py @@ -25,7 +25,6 @@ from lxml import etree from django.template.base import Template -from django.template.context import Context from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ImproperlyConfigured @@ -402,8 +401,7 @@ def _add_css_styles(self, epub_book): data = json.loads(self.config['theme']['custom'].encode('utf8')) tmpl = Template(content) - ctx = Context(data) - content = tmpl.render(ctx) + content = tmpl.render(data) except: logger.exception("Fails with custom theme.") diff --git a/lib/booktype/convert/mpdf/converter.py b/lib/booktype/convert/mpdf/converter.py index 8e01334a4..aaee99e52 100644 --- a/lib/booktype/convert/mpdf/converter.py +++ b/lib/booktype/convert/mpdf/converter.py @@ -29,7 +29,7 @@ from django.conf import settings from django.template.loader import render_to_string -from django.template.base import Context, Template +from django.template.base import Template from django.utils.translation import ugettext_lazy as _ from booktype.apps.convert import plugin @@ -507,8 +507,7 @@ def _write_style(self, book): self.config.update(custom) tmpl = Template(theme_style) - ctx = Context(self.config) - _style = tmpl.render(ctx) + _style = tmpl.render(self.config) theme_style = _style except: logger.exception("Writing styles failed for `%s` theme." % self.theme_name) diff --git a/lib/booktype/skeleton/base_settings.py.original b/lib/booktype/skeleton/base_settings.py.original index f1827f7b6..d5725bc6c 100644 --- a/lib/booktype/skeleton/base_settings.py.original +++ b/lib/booktype/skeleton/base_settings.py.original @@ -2,9 +2,6 @@ import os from unipath import Path -import djcelery -djcelery.setup_loader() - # DJANGO ADMIN ADMINS = ( # ('Your Name', 'your_email@domain.com'), @@ -264,8 +261,8 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_celery_results', 'compressor', - 'djcelery', 'rest_framework', 'rest_framework.authtoken', 'rest_framework_swagger', @@ -327,8 +324,11 @@ REST_FRAMEWORK = { } # CELERY -BROKER_URL = os.environ.get('BOOKTYPE_BROKER_URL', 'amqp://guest:guest@localhost:5672/') +CELERY_BROKER_URL = os.environ.get('BOOKTYPE_BROKER_URL', 'amqp://guest:guest@localhost:5672/') CELERY_ACCEPT_CONTENT = ['pickle', 'json', 'msgpack', 'yaml'] +CELERY_TASK_SERIALIZER = 'pickle' +CELERY_RESULT_SERIALIZER = 'pickle' +CELERY_RESULT_BACKEND = 'django-db' # set of default roles and corresponding permissions BOOKTYPE_DEFAULT_ROLES = { diff --git a/lib/booktype/skeleton/prod_settings.py.original b/lib/booktype/skeleton/prod_settings.py.original index ca558f135..f705f7bd6 100644 --- a/lib/booktype/skeleton/prod_settings.py.original +++ b/lib/booktype/skeleton/prod_settings.py.original @@ -41,7 +41,7 @@ REDIS_PASSWORD = os.environ.get('BOOKTYPE_REDIS_PASSWORD') REDIS_DB = os.environ.get('BOOKTYPE_REDIS_DB', 0) # CELERY -BROKER_URL = os.environ.get('BOOKTYPE_BROKER_URL', 'amqp://guest:guest@localhost:5672/') +CELERY_BROKER_URL = os.environ.get('BOOKTYPE_BROKER_URL', 'amqp://guest:guest@localhost:5672/') # DATABASE DATABASES = { diff --git a/lib/booktype/skeleton/stage_settings.py.original b/lib/booktype/skeleton/stage_settings.py.original index 6ed08dcb8..7392ed026 100644 --- a/lib/booktype/skeleton/stage_settings.py.original +++ b/lib/booktype/skeleton/stage_settings.py.original @@ -41,7 +41,7 @@ REDIS_PASSWORD = os.environ.get('BOOKTYPE_REDIS_PASSWORD') REDIS_DB = os.environ.get('BOOKTYPE_REDIS_DB', 0) # CELERY -BROKER_URL = os.environ.get('BOOKTYPE_BROKER_URL', 'amqp://guest:guest@localhost:5672/') +CELERY_BROKER_URL = os.environ.get('BOOKTYPE_BROKER_URL', 'amqp://guest:guest@localhost:5672/') # DATABASE DATABASES = { diff --git a/lib/booktypecontrol/forms.py b/lib/booktypecontrol/forms.py index 29e30c04e..4bdb453fe 100644 --- a/lib/booktypecontrol/forms.py +++ b/lib/booktypecontrol/forms.py @@ -556,11 +556,11 @@ def save_settings(self, request): if self.cleaned_data.get('send_email', False): t = template.loader.get_template( 'booktypecontrol/new_person_email.html') - content = t.render(Context({ + content = t.render({ "username": self.cleaned_data['username'], "password": self.cleaned_data['password2'], "server": settings.BOOKTYPE_URL - })) + }) from django.core.mail import EmailMultiAlternatives emails = [self.cleaned_data['email']] diff --git a/lib/booktypecontrol/views.py b/lib/booktypecontrol/views.py index c53fabbbf..a6f2bcc1f 100644 --- a/lib/booktypecontrol/views.py +++ b/lib/booktypecontrol/views.py @@ -31,7 +31,6 @@ from django.db import connection from django.db.models import Q, Count from django.contrib import messages -from django.template.base import Context from django.contrib.auth.models import User from django.http import HttpResponseRedirect from django.utils.translation import ugettext_lazy as _ @@ -420,11 +419,11 @@ def form_valid(self, form): if form.cleaned_data['send_login_data']: t = template.loader.get_template('booktypecontrol/password_changed_email.html') - content = t.render(Context({ + content = t.render({ "username": self.object.username, "password": form.cleaned_data['password2'], "short_message": form.cleaned_data['short_message'] - })) + }) msg = EmailMultiAlternatives( _('Your password was changed'), diff --git a/requirements/_base.txt b/requirements/_base.txt index 1e1382f27..11a88149b 100644 --- a/requirements/_base.txt +++ b/requirements/_base.txt @@ -1,7 +1,7 @@ -Django>1.8,<1.10.0 +Django==1.11 django-compressor==2.0 -celery>=3.1.18,<3.2 -django-celery==3.1.17 +celery==4.1.1 +django-celery-results==1.0.1 Unidecode lxml Pillow==2.9.0 @@ -13,6 +13,6 @@ python-ooxml django-rest-swagger==2.1.0 djangorestframework==3.5.3 Markdown==2.6.7 -django-filter==1.0.0 +django-filter==1.1.0 Ebooklib==0.16 GitPython==2.1.8 diff --git a/scripts/createbooktype b/scripts/createbooktype index 6f1aba67f..4c97e4dd4 100755 --- a/scripts/createbooktype +++ b/scripts/createbooktype @@ -21,7 +21,6 @@ import sys import os import argparse import string -import unipath import random @@ -193,17 +192,6 @@ def check_redis_available(): logln("[OK]") -def check_south_available(): - log("+ Trying to import South module. ") - - try: - import south - except ImportError: - raise InstallError() - else: - logln("[OK]") - - def check_unidecode_available(): log("+ Trying to import Unidecode module. ") @@ -588,10 +576,6 @@ sys.path.insert(0, '%(booki_path)s/') os.environ["DJANGO_SETTINGS_MODULE"] = "%(project_name)s_site.settings.%(profile)s" -# Initialise celery -import djcelery -djcelery.setup_loader() - # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. @@ -609,6 +593,71 @@ application = get_wsgi_application() else: logln("[OK]") +def include_celery(destination): + log("+ Import celery in {}_site __init__ file".format(get_project_name(destination))) + try: + f = open('%s/%s_site/__init__.py' % (destination, get_project_name(destination)), 'wt') + f.write(''' +from __future__ import absolute_import, unicode_literals + +# TODO we should have ability to change profile mode for celery +# in general we need ENV-variables driven settings, but it will require bigger changes +from .celery import app as celery_app + +__all__ = ['celery_app'] + + ''') + f.close() + except OSError: + raise InstallError() + else: + logln("[OK]") + + +def create_celery(destination): + log("+ Creating celery.py file. ".format(profile)) + + import booki + booki_path = os.path.abspath(os.path.dirname(booki.__file__) + '/..') + + try: + f = open('%s/%s_site/celery.py' % (destination, get_project_name(destination)), 'wt') + f.write(''' +from __future__ import absolute_import, unicode_literals + +import os +import sys +from unipath import Path +from celery import Celery + +BASE_DIR = Path(os.path.abspath(__file__)).ancestor(2) + +sys.path.insert(0, BASE_DIR) +sys.path.insert(0, BASE_DIR.child("lib")) +sys.path.insert(0, '{booki_path}') + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{project_name}_site.settings.dev') + +app = Celery('celery_booktype') + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks() + '''.format(booki_path=booki_path, project_name=get_project_name(destination))) + f.close() + except OSError: + raise InstallError() + else: + logln("[OK]") + + + + def create_supervisor(destination, profile, virtual_env): log("+ Creating supervisor_{}.conf file.".format(profile)) @@ -902,7 +951,6 @@ if __name__ == '__main__': check_lxml_available() check_pil_available() check_redis_available() - # check_south_available() check_unidecode_available() # show info about user id, group id and etc... @@ -954,10 +1002,14 @@ if __name__ == '__main__': # create urls create_urls(project_destination) - # create wsgi file + # create wsgi files for profile in PROFILES: create_wsgi(project_destination, profile, args.virtual_env) + # create celery files + include_celery(project_destination) + create_celery(project_destination) + # create supervisor file for wsgi (stage and prod only) for profile in PROFILES[1:]: create_supervisor(project_destination, profile, args.virtual_env) diff --git a/tests/func_settings.py b/tests/func_settings.py index c6d66a3af..58e46f543 100644 --- a/tests/func_settings.py +++ b/tests/func_settings.py @@ -101,7 +101,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', - 'djcelery', + 'django_celery_results', 'compressor', # list of booki apps diff --git a/tests/selenium_settings.py b/tests/selenium_settings.py index 5a61b68aa..1a6a6f966 100644 --- a/tests/selenium_settings.py +++ b/tests/selenium_settings.py @@ -139,7 +139,7 @@ 'django.contrib.staticfiles', 'compressor', - 'djcelery', + 'django_celery_results', # list of booki apps 'booki.editor', diff --git a/tests/settings.py b/tests/settings.py index b23e561ca..f87fc28fd 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -93,7 +93,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', - 'djcelery', + 'django_celery_results', # list of booki apps 'booki.editor', From 84bdccde3061768ba4ca68a1baa34e9a3112eb5d Mon Sep 17 00:00:00 2001 From: olegpshenichniy Date: Fri, 17 Aug 2018 15:29:29 +0200 Subject: [PATCH 2/8] BK-2085 Update dev settings. --- lib/booktype/skeleton/dev_settings.py.original | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/booktype/skeleton/dev_settings.py.original b/lib/booktype/skeleton/dev_settings.py.original index c99f1740d..dc93fe94f 100644 --- a/lib/booktype/skeleton/dev_settings.py.original +++ b/lib/booktype/skeleton/dev_settings.py.original @@ -3,10 +3,9 @@ import os from .base import * # WEB SITE URL -THIS_BOOKTYPE_SERVER = os.environ.get('BOOKTYPE_SERVER', '') +THIS_BOOKTYPE_SERVER = os.environ.get('BOOKTYPE_SERVER', '127.0.0.1:8000') -BOOKTYPE_URL = os.environ.get('BOOKTYPE_URL', '') -# BOOKTYPE_URL = 'http://{}'.format(THIS_BOOKTYPE_SERVER) +BOOKTYPE_URL = os.environ.get('BOOKTYPE_URL', 'http://{}'.format(THIS_BOOKTYPE_SERVER)) STATIC_URL = '{}/static/'.format(BOOKTYPE_URL) DATA_URL = '{}/data/'.format(BOOKTYPE_URL) From a1e60edc2546c68a29b948b978265d208c575104 Mon Sep 17 00:00:00 2001 From: olegpshenichniy Date: Fri, 17 Aug 2018 16:01:25 +0200 Subject: [PATCH 3/8] BK-2085 Update supervisor settings to fit latest celery requirements. --- scripts/createbooktype | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/createbooktype b/scripts/createbooktype index 4c97e4dd4..76bc9652e 100755 --- a/scripts/createbooktype +++ b/scripts/createbooktype @@ -664,7 +664,7 @@ def create_supervisor(destination, profile, virtual_env): s = '''[program:%(project_name)s-celery-worker] directory = %(project_directory)s -command = %(virtualenv)s/bin/python %(project_directory)s/manage_%(profile)s.py celery worker --concurrency=20 +command = %(virtualenv)s/bin/python -m celery worker --app=%(project_name)s_site --concurrency=20 user = www-data stopwaitsecs = 60 ''' % { From f459145ed655c4555104695d727776e7c2b0750e Mon Sep 17 00:00:00 2001 From: olegpshenichniy Date: Fri, 17 Aug 2018 16:09:50 +0200 Subject: [PATCH 4/8] BK-2085 Bumped up an ebooklib version. --- requirements/_base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/_base.txt b/requirements/_base.txt index 11a88149b..5539e4348 100644 --- a/requirements/_base.txt +++ b/requirements/_base.txt @@ -14,5 +14,5 @@ django-rest-swagger==2.1.0 djangorestframework==3.5.3 Markdown==2.6.7 django-filter==1.1.0 -Ebooklib==0.16 +Ebooklib==0.17 GitPython==2.1.8 From 889a517144f41c40510a1dbfeba947f588f96c70 Mon Sep 17 00:00:00 2001 From: olegpshenichniy Date: Fri, 24 Aug 2018 17:26:54 +0200 Subject: [PATCH 5/8] BK-2085 Bumped down an ebooklib version. Proper dev static/media settings. --- lib/booktype/skeleton/dev_settings.py.original | 4 ++-- requirements/_base.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/booktype/skeleton/dev_settings.py.original b/lib/booktype/skeleton/dev_settings.py.original index dc93fe94f..e89d5fa04 100644 --- a/lib/booktype/skeleton/dev_settings.py.original +++ b/lib/booktype/skeleton/dev_settings.py.original @@ -7,8 +7,8 @@ THIS_BOOKTYPE_SERVER = os.environ.get('BOOKTYPE_SERVER', '127.0.0.1:8000') BOOKTYPE_URL = os.environ.get('BOOKTYPE_URL', 'http://{}'.format(THIS_BOOKTYPE_SERVER)) -STATIC_URL = '{}/static/'.format(BOOKTYPE_URL) -DATA_URL = '{}/data/'.format(BOOKTYPE_URL) +STATIC_URL = '/static/' +DATA_URL = '/data/' MEDIA_URL = DATA_URL # URL where is publishing engine located. By default it is on local machine. diff --git a/requirements/_base.txt b/requirements/_base.txt index 5539e4348..11a88149b 100644 --- a/requirements/_base.txt +++ b/requirements/_base.txt @@ -14,5 +14,5 @@ django-rest-swagger==2.1.0 djangorestframework==3.5.3 Markdown==2.6.7 django-filter==1.1.0 -Ebooklib==0.17 +Ebooklib==0.16 GitPython==2.1.8 From b83818d33edb2fa0b2bffeb4f4771aa84254de96 Mon Sep 17 00:00:00 2001 From: olegpshenichniy Date: Mon, 1 Oct 2018 15:15:11 +0200 Subject: [PATCH 6/8] BK-2085 Bumped up django-compressor. --- requirements/_base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/_base.txt b/requirements/_base.txt index 3fb82a664..95689587a 100644 --- a/requirements/_base.txt +++ b/requirements/_base.txt @@ -1,5 +1,5 @@ Django==1.11 -django-compressor==2.0 +django-compressor==2.2 celery==4.1.1 django-celery-results==1.0.1 Unidecode From 19b79b88e80ad2d5146d588c250c29619714dc1b Mon Sep 17 00:00:00 2001 From: olegpshenichniy Date: Tue, 2 Oct 2018 11:07:12 +0200 Subject: [PATCH 7/8] Remove duplication in requirements. --- requirements/_base.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/_base.txt b/requirements/_base.txt index 95689587a..78f490e61 100644 --- a/requirements/_base.txt +++ b/requirements/_base.txt @@ -10,7 +10,6 @@ pika unipath django-braces python-ooxml -Ebooklib==0.17 django-rest-swagger==2.1.0 djangorestframework==3.5.3 Markdown==2.6.7 From fc11ee2ddbf084d37de493b1f3d9f70b918cbc85 Mon Sep 17 00:00:00 2001 From: olegpshenichniy Date: Tue, 2 Oct 2018 14:45:25 +0200 Subject: [PATCH 8/8] Fix mpdf theme style rendering. --- lib/booktype/convert/mpdf/converter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/booktype/convert/mpdf/converter.py b/lib/booktype/convert/mpdf/converter.py index aaee99e52..acf577654 100644 --- a/lib/booktype/convert/mpdf/converter.py +++ b/lib/booktype/convert/mpdf/converter.py @@ -29,7 +29,7 @@ from django.conf import settings from django.template.loader import render_to_string -from django.template.base import Template +from django import template from django.utils.translation import ugettext_lazy as _ from booktype.apps.convert import plugin @@ -506,8 +506,8 @@ def _write_style(self, book): custom = json.loads(custom.encode('utf-8')) self.config.update(custom) - tmpl = Template(theme_style) - _style = tmpl.render(self.config) + tmpl = template.Template(theme_style) + _style = tmpl.render(template.Context(self.config)) theme_style = _style except: logger.exception("Writing styles failed for `%s` theme." % self.theme_name)