Use fnpdjango and AttrCASBackend.
authorRadek Czajka <radekczajka@nowoczesnapolska.org.pl>
Thu, 27 Mar 2014 14:52:59 +0000 (15:52 +0100)
committerRadek Czajka <radekczajka@nowoczesnapolska.org.pl>
Thu, 27 Mar 2014 14:52:59 +0000 (15:52 +0100)
Remove some stale lib/ stuff.

15 files changed:
apps/catalogue/management/commands/assign_from_redmine.py
apps/catalogue/management/commands/merge_books.py
apps/catalogue/migrations/0003_from_hg.py
apps/catalogue/models/book.py
apps/catalogue/templatetags/common_tags.py
apps/catalogue/views.py
apps/cover/templates/cover/image_detail.html
lib/slughifi.py [deleted file]
lib/vstorage/__init__.py [deleted file]
lib/vstorage/hgui.py [deleted file]
lib/vstorage/tests.py [deleted file]
lib/wlapi/__init__.py [deleted file]
lib/wlapi/tests.py [deleted file]
redakcja/settings/common.py
requirements.txt

index 9f7b12d..03c0637 100644 (file)
@@ -12,7 +12,7 @@ from django.core.management.base import BaseCommand
 from django.core.management.color import color_style
 from django.db import transaction
 
-from slughifi import slughifi
+from fnpdjango.utils.text.slughifi import slughifi
 from catalogue.models import Chunk
 
 
index aec113e..62f8381 100644 (file)
@@ -8,7 +8,7 @@ from django.core.management.base import BaseCommand
 from django.core.management.color import color_style
 from django.db import transaction
 
-from slughifi import slughifi
+from fnpdjango.utils.text.slughifi import slughifi
 from catalogue.models import Book
 
 
index d836067..c0b4dad 100644 (file)
@@ -11,7 +11,7 @@ from south.db import db
 from south.v2 import DataMigration
 
 from django.conf import settings
-from slughifi import slughifi
+from fnpdjango.utils.text.slughifi import slughifi
 
 META_REGEX = re.compile(r'\s*<!--\s(.*?)-->', re.DOTALL | re.MULTILINE)
 STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE)
index 7c2bccc..5902ae9 100755 (executable)
@@ -8,7 +8,7 @@ from django.db import models, transaction
 from django.template.loader import render_to_string
 from django.utils.translation import ugettext_lazy as _
 from django.conf import settings
-from slughifi import slughifi
+from fnpdjango.utils.text.slughifi import slughifi
 
 
 import apiclient
index 6baf4e5..7f5d0e9 100755 (executable)
@@ -1,11 +1,6 @@
 from django import template
 register = template.Library()
 
-
-@register.filter
-def build_absolute_uri(uri, request):
-    return request.build_absolute_uri(uri)
-
 @register.filter
 def username(user):
     return ("%s %s" % (user.first_name, user.last_name)).lstrip() or user.username
index 237e4a7..ef8d83a 100644 (file)
@@ -142,7 +142,7 @@ def upload(request):
     if request.method == "POST":
         form = forms.DocumentsUploadForm(request.POST, request.FILES)
         if form.is_valid():
-            import slughifi
+            from fnpdjango.utils.text.slughifi import slughifi
 
             if request.user.is_authenticated():
                 creator = request.user
index ee6a762..d09a658 100755 (executable)
@@ -1,7 +1,7 @@
 {% extends "catalogue/base.html" %}
 {% load i18n %}
 {% load thumbnail %}
-{% load build_absolute_uri from common_tags %}
+{% load build_absolute_uri from fnp_common %}
 
 {% block content %}
 <h1>{% trans "Cover image" %}</h1>
diff --git a/lib/slughifi.py b/lib/slughifi.py
deleted file mode 100644 (file)
index fe5c9e3..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-import re
-from types import UnicodeType
-
-from django.template.defaultfilters import slugify
-
-# default unicode character mapping ( you may not see some chars, leave as is )
-char_map = {u'À': 'A', u'Á': 'A', u'Â': 'A', u'Ã': 'A', u'Ä': 'Ae', u'Å': 'A', u'Æ': 'A', u'Ā': 'A', u'Ą': 'A', u'Ă': 'A', u'Ç': 'C', u'Ć': 'C', u'Č': 'C', u'Ĉ': 'C', u'Ċ': 'C', u'Ď': 'D', u'Đ': 'D', u'È': 'E', u'É': 'E', u'Ê': 'E', u'Ë': 'E', u'Ē': 'E', u'Ę': 'E', u'Ě': 'E', u'Ĕ': 'E', u'Ė': 'E', u'Ĝ': 'G', u'Ğ': 'G', u'Ġ': 'G', u'Ģ': 'G', u'Ĥ': 'H', u'Ħ': 'H', u'Ì': 'I', u'Í': 'I', u'Î': 'I', u'Ï': 'I', u'Ī': 'I', u'Ĩ': 'I', u'Ĭ': 'I', u'Į': 'I', u'İ': 'I', u'IJ': 'IJ', u'Ĵ': 'J', u'Ķ': 'K', u'Ľ': 'K', u'Ĺ': 'K', u'Ļ': 'K', u'Ŀ': 'K', u'Ł': 'L', u'Ñ': 'N', u'Ń': 'N', u'Ň': 'N', u'Ņ': 'N', u'Ŋ': 'N', u'Ò': 'O', u'Ó': 'O', u'Ô': 'O', u'Õ': 'O', u'Ö': 'Oe', u'Ø': 'O', u'Ō': 'O', u'Ő': 'O', u'Ŏ': 'O', u'Œ': 'OE', u'Ŕ': 'R', u'Ř': 'R', u'Ŗ': 'R', u'Ś': 'S', u'Ş': 'S', u'Ŝ': 'S', u'Ș': 'S', u'Š': 'S', u'Ť': 'T', u'Ţ': 'T', u'Ŧ': 'T', u'Ț': 'T', u'Ù': 'U', u'Ú': 'U', u'Û': 'U', u'Ü': 'Ue', u'Ū': 'U', u'Ů': 'U', u'Ű': 'U', u'Ŭ': 'U', u'Ũ': 'U', u'Ų': 'U', u'Ŵ': 'W', u'Ŷ': 'Y', u'Ÿ': 'Y', u'Ý': 'Y', u'Ź': 'Z', u'Ż': 'Z', u'Ž': 'Z', u'à': 'a', u'á': 'a', u'â': 'a', u'ã': 'a', u'ä': 'ae', u'ā': 'a', u'ą': 'a', u'ă': 'a', u'å': 'a', u'æ': 'ae', u'ç': 'c', u'ć': 'c', u'č': 'c', u'ĉ': 'c', u'ċ': 'c', u'ď': 'd', u'đ': 'd', u'è': 'e', u'é': 'e', u'ê': 'e', u'ë': 'e', u'ē': 'e', u'ę': 'e', u'ě': 'e', u'ĕ': 'e', u'ė': 'e', u'ƒ': 'f', u'ĝ': 'g', u'ğ': 'g', u'ġ': 'g', u'ģ': 'g', u'ĥ': 'h', u'ħ': 'h', u'ì': 'i', u'í': 'i', u'î': 'i', u'ï': 'i', u'ī': 'i', u'ĩ': 'i', u'ĭ': 'i', u'į': 'i', u'ı': 'i', u'ij': 'ij', u'ĵ': 'j', u'ķ': 'k', u'ĸ': 'k', u'ł': 'l', u'ľ': 'l', u'ĺ': 'l', u'ļ': 'l', u'ŀ': 'l', u'ñ': 'n', u'ń': 'n', u'ň': 'n', u'ņ': 'n', u'ʼn': 'n', u'ŋ': 'n', u'ò': 'o', u'ó': 'o', u'ô': 'o', u'õ': 'o', u'ö': 'oe', u'ø': 'o', u'ō': 'o', u'ő': 'o', u'ŏ': 'o', u'œ': 'oe', u'ŕ': 'r', u'ř': 'r', u'ŗ': 'r', u'ś': 's', u'š': 's', u'ť': 't', u'ù': 'u', u'ú': 'u', u'û': 'u', u'ü': 'ue', u'ū': 'u', u'ů': 'u', u'ű': 'u', u'ŭ': 'u', u'ũ': 'u', u'ų': 'u', u'ŵ': 'w', u'ÿ': 'y', u'ý': 'y', u'ŷ': 'y', u'ż': 'z', u'ź': 'z', u'ž': 'z', u'ß': 'ss', u'ſ': 'ss', u'Α': 'A', u'Ά': 'A', u'Ἀ': 'A', u'Ἁ': 'A', u'Ἂ': 'A', u'Ἃ': 'A', u'Ἄ': 'A', u'Ἅ': 'A', u'Ἆ': 'A', u'Ἇ': 'A', u'ᾈ': 'A', u'ᾉ': 'A', u'ᾊ': 'A', u'ᾋ': 'A', u'ᾌ': 'A', u'ᾍ': 'A', u'ᾎ': 'A', u'ᾏ': 'A', u'Ᾰ': 'A', u'Ᾱ': 'A', u'Ὰ': 'A', u'Ά': 'A', u'ᾼ': 'A', u'Β': 'B', u'Γ': 'G', u'Δ': 'D', u'Ε': 'E', u'Έ': 'E', u'Ἐ': 'E', u'Ἑ': 'E', u'Ἒ': 'E', u'Ἓ': 'E', u'Ἔ': 'E', u'Ἕ': 'E', u'Έ': 'E', u'Ὲ': 'E', u'Ζ': 'Z', u'Η': 'I', u'Ή': 'I', u'Ἠ': 'I', u'Ἡ': 'I', u'Ἢ': 'I', u'Ἣ': 'I', u'Ἤ': 'I', u'Ἥ': 'I', u'Ἦ': 'I', u'Ἧ': 'I', u'ᾘ': 'I', u'ᾙ': 'I', u'ᾚ': 'I', u'ᾛ': 'I', u'ᾜ': 'I', u'ᾝ': 'I', u'ᾞ': 'I', u'ᾟ': 'I', u'Ὴ': 'I', u'Ή': 'I', u'ῌ': 'I', u'Θ': 'TH', u'Ι': 'I', u'Ί': 'I', u'Ϊ': 'I', u'Ἰ': 'I', u'Ἱ': 'I', u'Ἲ': 'I', u'Ἳ': 'I', u'Ἴ': 'I', u'Ἵ': 'I', u'Ἶ': 'I', u'Ἷ': 'I', u'Ῐ': 'I', u'Ῑ': 'I', u'Ὶ': 'I', u'Ί': 'I', u'Κ': 'K', u'Λ': 'L', u'Μ': 'M', u'Ν': 'N', u'Ξ': 'KS', u'Ο': 'O', u'Ό': 'O', u'Ὀ': 'O', u'Ὁ': 'O', u'Ὂ': 'O', u'Ὃ': 'O', u'Ὄ': 'O', u'Ὅ': 'O', u'Ὸ': 'O', u'Ό': 'O', u'Π': 'P', u'Ρ': 'R', u'Ῥ': 'R', u'Σ': 'S', u'Τ': 'T', u'Υ': 'Y', u'Ύ': 'Y', u'Ϋ': 'Y', u'Ὑ': 'Y', u'Ὓ': 'Y', u'Ὕ': 'Y', u'Ὗ': 'Y', u'Ῠ': 'Y', u'Ῡ': 'Y', u'Ὺ': 'Y', u'Ύ': 'Y', u'Φ': 'F', u'Χ': 'X', u'Ψ': 'PS', u'Ω': 'O', u'Ώ': 'O', u'Ὠ': 'O', u'Ὡ': 'O', u'Ὢ': 'O', u'Ὣ': 'O', u'Ὤ': 'O', u'Ὥ': 'O', u'Ὦ': 'O', u'Ὧ': 'O', u'ᾨ': 'O', u'ᾩ': 'O', u'ᾪ': 'O', u'ᾫ': 'O', u'ᾬ': 'O', u'ᾭ': 'O', u'ᾮ': 'O', u'ᾯ': 'O', u'Ὼ': 'O', u'Ώ': 'O', u'ῼ': 'O', u'α': 'a', u'ά': 'a', u'ἀ': 'a', u'ἁ': 'a', u'ἂ': 'a', u'ἃ': 'a', u'ἄ': 'a', u'ἅ': 'a', u'ἆ': 'a', u'ἇ': 'a', u'ᾀ': 'a', u'ᾁ': 'a', u'ᾂ': 'a', u'ᾃ': 'a', u'ᾄ': 'a', u'ᾅ': 'a', u'ᾆ': 'a', u'ᾇ': 'a', u'ὰ': 'a', u'ά': 'a', u'ᾰ': 'a', u'ᾱ': 'a', u'ᾲ': 'a', u'ᾳ': 'a', u'ᾴ': 'a', u'ᾶ': 'a', u'ᾷ': 'a', u'β': 'b', u'γ': 'g', u'δ': 'd', u'ε': 'e', u'έ': 'e', u'ἐ': 'e', u'ἑ': 'e', u'ἒ': 'e', u'ἓ': 'e', u'ἔ': 'e', u'ἕ': 'e', u'ὲ': 'e', u'έ': 'e', u'ζ': 'z', u'η': 'i', u'ή': 'i', u'ἠ': 'i', u'ἡ': 'i', u'ἢ': 'i', u'ἣ': 'i', u'ἤ': 'i', u'ἥ': 'i', u'ἦ': 'i', u'ἧ': 'i', u'ᾐ': 'i', u'ᾑ': 'i', u'ᾒ': 'i', u'ᾓ': 'i', u'ᾔ': 'i', u'ᾕ': 'i', u'ᾖ': 'i', u'ᾗ': 'i', u'ὴ': 'i', u'ή': 'i', u'ῂ': 'i', u'ῃ': 'i', u'ῄ': 'i', u'ῆ': 'i', u'ῇ': 'i', u'θ': 'th', u'ι': 'i', u'ί': 'i', u'ϊ': 'i', u'ΐ': 'i', u'ἰ': 'i', u'ἱ': 'i', u'ἲ': 'i', u'ἳ': 'i', u'ἴ': 'i', u'ἵ': 'i', u'ἶ': 'i', u'ἷ': 'i', u'ὶ': 'i', u'ί': 'i', u'ῐ': 'i', u'ῑ': 'i', u'ῒ': 'i', u'ΐ': 'i', u'ῖ': 'i', u'ῗ': 'i', u'κ': 'k', u'λ': 'l', u'μ': 'm', u'ν': 'n', u'ξ': 'ks', u'ο': 'o', u'ό': 'o', u'ὀ': 'o', u'ὁ': 'o', u'ὂ': 'o', u'ὃ': 'o', u'ὄ': 'o', u'ὅ': 'o', u'ὸ': 'o', u'ό': 'o', u'π': 'p', u'ρ': 'r', u'ῤ': 'r', u'ῥ': 'r', u'σ': 's', u'ς': 's', u'τ': 't', u'υ': 'y', u'ύ': 'y', u'ϋ': 'y', u'ΰ': 'y', u'ὐ': 'y', u'ὑ': 'y', u'ὒ': 'y', u'ὓ': 'y', u'ὔ': 'y', u'ὕ': 'y', u'ὖ': 'y', u'ὗ': 'y', u'ὺ': 'y', u'ύ': 'y', u'ῠ': 'y', u'ῡ': 'y', u'ῢ': 'y', u'ΰ': 'y', u'ῦ': 'y', u'ῧ': 'y', u'φ': 'f', u'χ': 'x', u'ψ': 'ps', u'ω': 'o', u'ώ': 'o', u'ὠ': 'o', u'ὡ': 'o', u'ὢ': 'o', u'ὣ': 'o', u'ὤ': 'o', u'ὥ': 'o', u'ὦ': 'o', u'ὧ': 'o', u'ᾠ': 'o', u'ᾡ': 'o', u'ᾢ': 'o', u'ᾣ': 'o', u'ᾤ': 'o', u'ᾥ': 'o', u'ᾦ': 'o', u'ᾧ': 'o', u'ὼ': 'o', u'ώ': 'o', u'ῲ': 'o', u'ῳ': 'o', u'ῴ': 'o', u'ῶ': 'o', u'ῷ': 'o', u'¨': '', u'΅': '', u'᾿': '', u'῾': '', u'῍': '', u'῝': '', u'῎': '', u'῞': '', u'῏': '', u'῟': '', u'῀': '', u'῁': '', u'΄': '', u'΅': '', u'`': '', u'῭': '', u'ͺ': '', u'᾽': '', u'А': 'A', u'Б': 'B', u'В': 'V', u'Г': 'G', u'Д': 'D', u'Е': 'E', u'Ё': 'E', u'Ж': 'ZH', u'З': 'Z', u'И': 'I', u'Й': 'I', u'К': 'K', u'Л': 'L', u'М': 'M', u'Н': 'N', u'О': 'O', u'П': 'P', u'Р': 'R', u'С': 'S', u'Т': 'T', u'У': 'U', u'Ф': 'F', u'Х': 'KH', u'Ц': 'TS', u'Ч': 'CH', u'Ш': 'SH', u'Щ': 'SHCH', u'Ы': 'Y', u'Э': 'E', u'Ю': 'YU', u'Я': 'YA', u'а': 'A', u'б': 'B', u'в': 'V', u'г': 'G', u'д': 'D', u'е': 'E', u'ё': 'E', u'ж': 'ZH', u'з': 'Z', u'и': 'I', u'й': 'I', u'к': 'K', u'л': 'L', u'м': 'M', u'н': 'N', u'о': 'O', u'п': 'P', u'р': 'R', u'с': 'S', u'т': 'T', u'у': 'U', u'ф': 'F', u'х': 'KH', u'ц': 'TS', u'ч': 'CH', u'ш': 'SH', u'щ': 'SHCH', u'ы': 'Y', u'э': 'E', u'ю': 'YU', u'я': 'YA', u'Ъ': '', u'ъ': '', u'Ь': '', u'ь': '', u'ð': 'd', u'Ð': 'D', u'þ': 'th', u'Þ': 'TH',
-            u'ა': 'a', u'ბ': 'b', u'გ': 'g', u'დ': 'd', u'ე': 'e', u'ვ': 'v', u'ზ': 'z', u'თ': 't', u'ი': 'i', u'კ': 'k', u'ლ': 'l', u'მ': 'm', u'ნ': 'n', u'ო': 'o', u'პ': 'p', u'ჟ': 'zh', u'რ': 'r', u'ს': 's', u'ტ': 't', u'უ': 'u', u'ფ': 'p', u'ქ': 'k', u'ღ': 'gh', u'ყ': 'q', u'შ': 'sh', u'ჩ': 'ch', u'ც': 'ts', u'ძ': 'dz', u'წ': 'ts', u'ჭ': 'ch', u'ხ': 'kh', u'ჯ': 'j', u'ჰ': 'h' }
-
-def replace_char(m):
-    char = m.group()
-    if char_map.has_key(char):
-        return char_map[char]
-    else:
-        return char
-
-def slughifi(value, do_slugify=True, overwrite_char_map={}):
-    """
-        High Fidelity slugify - slughifi.py, v 0.1
-
-        Examples :
-
-        >>> text = 'C\'est déjà l\'été.'
-
-        >>> slughifi(text)
-        'cest-deja-lete'
-
-        >>> slughifi(text, overwrite_char_map={u'\'': '-',})
-        'c-est-deja-l-ete'
-
-        >>> slughifi(text, do_slugify=False)
-        "C'est deja l'ete."
-
-        # Normal slugify removes accented characters
-        >>> slugify(text)
-        'cest-dj-lt'
-
-    """
-
-    # unicodification
-    if type(value) != UnicodeType:
-        value = unicode(value, 'utf-8', 'ignore')
-
-    # overwrite chararcter mapping
-    char_map.update(overwrite_char_map)
-
-    # try to replace chars
-    value = re.sub('[^a-zA-Z0-9\\s\\-]{1}', replace_char, value)
-
-    # apply django default slugify
-    if do_slugify:
-        value = slugify(value)
-
-    return value.encode('ascii', 'ignore')
-
diff --git a/lib/vstorage/__init__.py b/lib/vstorage/__init__.py
deleted file mode 100644 (file)
index 2708ed7..0000000
+++ /dev/null
@@ -1,445 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-import os
-import tempfile
-import datetime
-import mimetypes
-import urllib
-import functools
-
-import logging
-logger = logging.getLogger('fnp.hazlenut.vstorage')
-
-# Note: we have to set these before importing Mercurial
-os.environ['HGENCODING'] = 'utf-8'
-os.environ['HGMERGE'] = "internal:merge"
-
-import mercurial.hg
-import mercurial.revlog
-import mercurial.util
-
-from vstorage.hgui import SilentUI
-
-
-def urlquote(url, safe='/'):
-    """Quotes URL
-
-    >>> urlquote(u'Za\u017c\xf3\u0142\u0107 g\u0119\u015bl\u0105 ja\u017a\u0144')
-    'Za%C5%BC%C3%B3%C5%82%C4%87%20g%C4%99%C5%9Bl%C4%85%20ja%C5%BA%C5%84'
-    """
-    return urllib.quote(url.encode('utf-8', 'ignore'), safe)
-
-
-def urlunquote(url):
-    """Unqotes URL
-
-    # >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84')
-    # u'Za\u017c\xf3\u0142\u0107_g\u0119\u015bl\u0105 ja\u017a\u0144'
-    """
-    return unicode(urllib.unquote(url), 'utf-8', 'ignore')
-
-
-def find_repo_path(path):
-    """Go up the directory tree looking for a Mercurial repository (a directory containing a .hg subdirectory)."""
-    while not os.path.isdir(os.path.join(path, ".hg")):
-        old_path, path = path, os.path.dirname(path)
-        if path == old_path:
-            return None
-    return path
-
-
-def with_working_copy_locked(func):
-    """A decorator for locking the repository when calling a method."""
-
-    @functools.wraps(func)
-    def wrapped(self, *args, **kwargs):
-        """Wrap the original function in locks."""
-        wlock = self.repo.wlock()
-        try:
-            return func(self, *args, **kwargs)
-        finally:
-            wlock.release()
-    return wrapped
-
-
-def with_storage_locked(func):
-    """A decorator for locking the repository when calling a method."""
-
-    @functools.wraps(func)
-    def wrapped(self, *args, **kwargs):
-        """Wrap the original function in locks."""
-        lock = self.repo.lock()
-        try:
-            return func(self, *args, **kwargs)
-        finally:
-            lock.release()
-    return wrapped
-
-
-def guess_mime(file_name):
-    """
-    Guess file's mime type based on extension.
-    Default of text/x-wiki for files without an extension.
-
-    >>> guess_mime('something.txt')
-    'text/plain'
-    >>> guess_mime('SomePage')
-    'text/x-wiki'
-    >>> guess_mime(u'ąęśUnicodePage')
-    'text/x-wiki'
-    >>> guess_mime('image.png')
-    'image/png'
-    >>> guess_mime('style.css')
-    'text/css'
-    >>> guess_mime('archive.tar.gz')
-    'archive/gzip'
-    """
-
-    mime, encoding = mimetypes.guess_type(file_name, strict=False)
-    if encoding:
-        mime = 'archive/%s' % encoding
-    if mime is None:
-        mime = 'text/x-wiki'
-    return mime
-
-
-class DocumentNotFound(Exception):
-    pass
-
-
-class VersionedStorage(object):
-    """
-    Provides means of storing text pages and keeping track of their
-    change history, using Mercurial repository as the storage method.
-    """
-
-    def __init__(self, path, charset=None):
-        """
-        Takes the path to the directory where the pages are to be kept.
-        If the directory doen't exist, it will be created. If it's inside
-        a Mercurial repository, that repository will be used, otherwise
-        a new repository will be created in it.
-        """
-
-        self.charset = charset or 'utf-8'
-        self.path = path
-        if not os.path.exists(self.path):
-            os.makedirs(self.path)
-        self.repo_path = find_repo_path(self.path)
-
-        self.ui = SilentUI()
-
-        if self.repo_path is None:
-            self.repo_path = self.path
-            create = True
-        else:
-            create = False
-
-        self.repo_prefix = self.path[len(self.repo_path):].strip('/')
-        self.repo = mercurial.hg.repository(self.ui, self.repo_path,
-                                            create=create)
-
-    def reopen(self):
-        """Close and reopen the repo, to make sure we are up to date."""
-        self.repo = mercurial.hg.repository(self.ui, self.repo_path)
-
-    def _file_path(self, title, type='.xml'):
-        """ Return plain version if exists in repo, add extension otherwise. """
-        path = os.path.join(self.path, urlquote(title, safe=''))
-        if type and self._title_to_file(title, '') not in self.repo['tip']:
-            path += type
-        return path
-
-    def _title_to_file(self, title, type=".xml"):
-        """ Return plain version if exists in repo, add extension otherwise. """
-        path = os.path.join(self.repo_prefix, urlquote(title, safe=''))
-        if type and path not in self.repo['tip']:
-            path += type
-        return path
-
-    def _file_to_title(self, filename):
-        assert filename.startswith(self.repo_prefix)
-        name = filename[len(self.repo_prefix):].strip('/').rsplit('.', 1)[0]
-        return urlunquote(name)
-
-    def __contains__(self, title):
-        return self._title_to_file(title) in self.repo['tip']
-
-    def __iter__(self):
-        return self.all_pages()
-
-    def merge_changes(self, changectx, repo_file, text, user, parent):
-        """Commits and merges conflicting changes in the repository."""
-        tip_node = changectx.node()
-        filectx = changectx[repo_file].filectx(parent)
-        parent_node = filectx.changectx().node()
-
-        self.repo.dirstate.setparents(parent_node)
-        node = self._commit([repo_file], text, user)
-
-        partial = lambda filename: repo_file == filename
-
-        # If p1 is equal to p2, there is no work to do. Even the dirstate is correct.
-        p1, p2 = self.repo[None].parents()[0], self.repo[tip_node]
-        if p1 == p2:
-            return text
-
-        try:
-            mercurial.merge.update(self.repo, tip_node, True, False, partial)
-            msg = 'merge of edit conflict'
-        except mercurial.util.Abort:
-            msg = 'failed merge of edit conflict'
-
-        self.repo.dirstate.setparents(tip_node, node)
-        # Mercurial 1.1 and later need updating the merge state
-        try:
-            mercurial.merge.mergestate(self.repo).mark(repo_file, "r")
-        except (AttributeError, KeyError):
-            pass
-        return msg
-
-    @with_working_copy_locked
-    @with_storage_locked
-    def save_file(self, title, file_name, **kwargs):
-        """Save an existing file as specified page."""
-
-        author = kwargs.get('author', u'anonymous').encode('utf-8')
-        comment = kwargs.get('comment', u'Empty comment.').encode('utf-8')
-        parent = kwargs.get('parent', None)
-
-        repo_file = self._title_to_file(title)
-        file_path = self._file_path(title)
-        mercurial.util.rename(file_name, file_path)
-        changectx = self._changectx()
-
-        try:
-            filectx_tip = changectx[repo_file]
-            current_page_rev = filectx_tip.filerev()
-        except mercurial.revlog.LookupError:
-            self.repo[None].add([repo_file])
-            current_page_rev = -1
-
-        if parent is not None and current_page_rev != parent:
-            msg = self.merge_changes(changectx, repo_file, comment, author, parent)
-            author = '<wiki>'
-            comment = msg.encode('utf-8')
-
-        logger.debug("Commiting %r", repo_file)
-
-        self._commit([repo_file], comment, author)
-
-    def save_data(self, title, data, **kwargs):
-        """Save data as specified page."""
-        try:
-            temp_path = tempfile.mkdtemp(dir=self.path)
-            file_path = os.path.join(temp_path, 'saved')
-            f = open(file_path, "wb")
-            f.write(data)
-            f.close()
-
-            return self.save_file(title=title, file_name=file_path, **kwargs)
-        finally:
-            try:
-                os.unlink(file_path)
-            except OSError:
-                pass
-            try:
-                os.rmdir(temp_path)
-            except OSError:
-                pass
-
-    def save_text(self, **kwargs):
-        """Save text as specified page, encoded to charset."""
-        text = kwargs.pop('text')
-        return self.save_data(data=text.encode(self.charset), **kwargs)
-
-    def _commit(self, files, comment, user):
-        match = mercurial.match.exact(self.repo_path, '', list(files))
-        return self.repo.commit(match=match, text=comment, user=user, force=True)
-
-    @with_working_copy_locked
-    @with_storage_locked
-    def delete_page(self, title, author=u'', comment=u''):
-        user = author.encode('utf-8') or 'anon'
-        text = comment.encode('utf-8') or 'deleted'
-        repo_file = self._title_to_file(title)
-        file_path = self._file_path(title)
-        try:
-            os.unlink(file_path)
-        except OSError:
-            pass
-        self.repo[None].remove([repo_file])
-        self._commit([repo_file], text, user)
-
-    def page_text(self, title, revision=None):
-        """Read unicode text of a page."""
-        ctx = self._find_filectx(title, revision)
-
-        if ctx is None:
-            raise DocumentNotFound(title)
-
-        return ctx.data().decode(self.charset, 'replace'), ctx.filerev()
-
-    def page_text_by_tag(self, title, tag):
-        """Read unicode text of a taged page."""
-        fname = self._title_to_file(title)
-        tag = u"{fname}#{tag}".format(**locals()).encode('utf-8')
-
-        try:
-            ctx = self.repo[tag][fname]
-            return ctx.data().decode(self.charset, 'replace'), ctx.filerev()
-        except IndexError:
-            raise DocumentNotFound(fname)
-
-    @with_working_copy_locked
-    def page_file_meta(self, title):
-        """Get page's inode number, size and last modification time."""
-        try:
-            (_st_mode, st_ino, _st_dev, _st_nlink, _st_uid, _st_gid, st_size,
-             _st_atime, st_mtime, _st_ctime) = os.stat(self._file_path(title))
-        except OSError:
-            return 0, 0, 0
-        return st_ino, st_size, st_mtime
-
-    @with_working_copy_locked
-    def page_meta(self, title, revision=None):
-        """Get page's revision, date, last editor and his edit comment."""
-        fctx = self._find_filectx(title, revision)
-
-        if fctx is None:
-            raise DocumentNotFound(title)
-
-        return {
-            "revision": fctx.filerev(),
-            "date": datetime.datetime.fromtimestamp(fctx.date()[0]),
-            "author": fctx.user().decode("utf-8", 'replace'),
-            "comment": fctx.description().decode("utf-8", 'replace'),
-        }
-
-    def repo_revision(self):
-        return self.repo['tip'].rev()
-
-    def _changectx(self):
-        return self.repo['tip']
-
-    def page_mime(self, title):
-        """
-        Guess page's mime type based on corresponding file name.
-        Default ot text/x-wiki for files without an extension.
-        """
-        return guess_mime(self._file_path(title))
-
-    def _find_filectx(self, title, rev=None):
-        """
-        Find the revision of the file in repo.
-        Only look for files still existing in repo's tip.
-        """
-        tip = self._changectx()
-        file = self._title_to_file(title)
-        logging.info('Looking for %s', file)
-        if file in tip:
-            fctx = tip[file]
-        else:
-            file = self._title_to_file(title, type='')
-            logging.info('.xml not found, trying plain')
-            if file in tip:
-                fctx = tip[file]
-            else:
-                raise DocumentNotFound(title)
-
-        if rev is not None:
-            fctx = fctx.filectx(rev)
-            fctx.filerev()
-        return fctx
-
-    def page_history(self, title):
-        """Iterate over the page's history."""
-
-        filectx_tip = self._find_filectx(title)
-
-        maxrev = filectx_tip.filerev()
-        minrev = 0
-        for rev in range(maxrev, minrev - 1, -1):
-            filectx = filectx_tip.filectx(rev)
-            date = datetime.datetime.fromtimestamp(filectx.date()[0])
-            author = filectx.user().decode('utf-8', 'replace')
-            comment = filectx.description().decode("utf-8", 'replace')
-            tags = [t.rsplit('#', 1)[-1] for t in filectx.changectx().tags() if '#' in t]
-
-            yield {
-                "version": rev,
-                "date": date,
-                "author": author,
-                "description": comment,
-                "tag": tags,
-            }
-
-    @with_working_copy_locked
-    def add_page_tag(self, title, rev, tag, user, doctag=True):
-        ctitle = self._title_to_file(title)
-
-        if doctag:
-            tag = u"{ctitle}#{tag}".format(**locals()).encode('utf-8')
-
-        message = u"Assigned tag {tag!r} to version {rev!r} of {ctitle!r}".format(**locals()).encode('utf-8')
-
-        fctx = self._find_filectx(title, rev)
-        self.repo.tag(
-            names=tag, node=fctx.node(), local=False,
-            user=user, message=message, date=None,
-        )
-
-    def history(self):
-        """Iterate over the history of entire wiki."""
-
-        changectx = self._changectx()
-        maxrev = changectx.rev()
-        minrev = 0
-        for wiki_rev in range(maxrev, minrev - 1, -1):
-            change = self.repo.changectx(wiki_rev)
-            date = datetime.datetime.fromtimestamp(change.date()[0])
-            author = change.user().decode('utf-8', 'replace')
-            comment = change.description().decode("utf-8", 'replace')
-            for repo_file in change.files():
-                if repo_file.startswith(self.repo_prefix):
-                    title = self._file_to_title(repo_file)
-                    try:
-                        rev = change[repo_file].filerev()
-                    except mercurial.revlog.LookupError:
-                        rev = -1
-                    yield title, rev, date, author, comment
-
-    def all_pages(self, type=''):
-        tip = self.repo['tip']
-        """Iterate over the titles of all pages in the wiki."""
-        return [self._file_to_title(filename) for filename in tip
-                  if not filename.startswith('.')
-                    and filename.endswith(type) ]
-
-    def changed_since(self, rev):
-        """Return all pages that changed since specified repository revision."""
-
-        try:
-            last = self.repo.lookup(int(rev))
-        except IndexError:
-            for page in self.all_pages():
-                yield page
-                return
-        current = self.repo.lookup('tip')
-        status = self.repo.status(current, last)
-        modified, added, removed, deleted, unknown, ignored, clean = status
-        for filename in modified + added + removed + deleted:
-            if filename.startswith(self.repo_prefix):
-                yield self._file_to_title(filename)
-
-    def revert(self, pageid, rev, **commit_args):
-        """ Make the given version of page the current version (reverting changes). """
-
-        # Find the old version
-        fctx = self._find_filectx(pageid, rev)
-
-        # Restore the contents
-        self.save_data(pageid, fctx.data(), **commit_args)
diff --git a/lib/vstorage/hgui.py b/lib/vstorage/hgui.py
deleted file mode 100644 (file)
index 36c6e23..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-"""
-    Mercurial ui module replacement.
-"""
-
-import mercurial.ui
-import logging
-
-class SilentUI(mercurial.ui.ui):
-
-    def __init__(self, *args, **kwargs):
-        super(SilentUI, self).__init__(*args, **kwargs)
-
-        # make sure this doesn't collide with anything in Mercurial
-        self.__logger = logging.getLogger('mercurial')
-
-    def _is_trusted(self, fd, filename):
-        """ Checks if config file is trusted - on server side, this isn't very useful. """
-        return True
-
-    def write(self, *args):
-        if self._buffers:
-            self._buffers[-1].extend([str(a) for a in args])
-        else:
-            self.__logger.info(''.join(args))
-
-    def write_err(self, *args):
-        self.__logger.error(''.join(args))
-
-    def flush(self):
-        pass
-
-    def interactive(self):
-        return False
-
-    def _readline(self, prompt=''):
-        return u''
-
-    def status(self, *msg):
-        self.__logger.debug(''.join(msg))
-
-    def warn(self, *msg):
-        self.__logger.warn(''.join(msg))
-
-    def note(self, *msg):
-        self.__logger.info(''.join(msg))
-
-    def debug(self, *msg):
-        self.__logger.debug(''.join(msg))
-
-    def edit(self, text, user):
-        return text
-
-    def traceback(self, exc=None):
-        if exc is not None: self.__logger.exception()
-
-    def progress(self, *args, **kwargs):
-        pass
diff --git a/lib/vstorage/tests.py b/lib/vstorage/tests.py
deleted file mode 100644 (file)
index 25e2b06..0000000
+++ /dev/null
@@ -1,241 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-
-import os
-import tempfile
-from nose.tools import *
-from nose.core import runmodule
-
-import vstorage
-
-def clear_directory(top):
-    for root, dirs, files in os.walk(top, topdown=False):
-        for name in files:
-            os.remove(os.path.join(root, name))
-        for name in dirs:
-            os.rmdir(os.path.join(root, name))
-    try:
-        os.removedirs(top)
-    except OSError:
-        pass
-
-
-class TestVersionedStorage(object):
-    def setUp(self):
-        self.repo_path = tempfile.mkdtemp()
-        self.repo = vstorage.VersionedStorage(self.repo_path)
-
-    def tearDown(self):
-        clear_directory(self.repo_path)
-
-    def test_save_text(self):
-        text = u"test text"
-        title = u"test title"
-        author = u"test author"
-        comment = u"test comment"
-
-        self.repo.save_text(
-            title=title,
-            text=text,
-            author=author,
-            comment=comment,
-            parent=None,
-        )
-
-        saved_text, rev = self.repo.page_text(title)
-        assert_equal(saved_text, text)
-        assert_equal(rev, 0)
-
-    def test_save_text_noparent(self):
-        text = u"test text"
-        title = u"test title"
-        author = u"test author"
-        comment = u"test comment"
-
-        self.repo.save_text(title=title,
-                    text=text, author=author,
-                    comment=comment, parent=None)
-
-
-        saved_text, rev = self.repo.page_text(title)
-        assert_equal(saved_text, text)
-        assert_equal(rev, 0)
-
-    def test_save_merge_no_conflict(self):
-        text = u"test\ntext"
-        title = u"test title"
-        author = u"test author"
-        comment = u"test comment"
-        self.repo.save_text(title=title,
-                    text=text, author=author,
-                    comment=comment, parent=None)
-        self.repo.save_text(title=title,
-                    text=text, author=author,
-                    comment=comment, parent=None)
-
-        saved_text, rev = self.repo.page_text(title)
-        assert_equal(saved_text, text)
-        assert_equal(rev, 0)
-
-    def test_save_merge_line_conflict(self):
-        text = u"test\ntest\n"
-        text1 = u"test\ntext\n"
-        text2 = u"text\ntest\n"
-        title = u"test title"
-        author = u"test author"
-        comment = u"test comment"
-
-        self.repo.save_text(title=title,
-                    text=text, author=author,
-                    comment=comment, parent=None)
-
-        saved_text, rev = self.repo.page_text(title)
-        assert_equal(saved_text, text)
-        assert_equal(rev, 0)
-
-        self.repo.save_text(title=title,
-                    text=text1, author=author,
-                    comment=comment, parent=0)
-
-        saved_text, rev = self.repo.page_text(title)
-        assert_equal(saved_text, text1)
-        assert_equal(rev, 1)
-
-        self.repo.save_text(title=title,
-                    text=text2, author=author,
-                    comment=comment, parent=0)
-
-        saved_text, rev = self.repo.page_text(title)
-        # Other conflict markers placement can also be correct
-        assert_equal(saved_text, u'''\
-text
-test
-<<<<<<< local
-=======
-text
->>>>>>> other
-''')
-
-    def test_delete(self):
-        text = u"text test"
-        title = u"test title"
-        author = u"test author"
-        comment = u"test comment"
-        self.repo.save_text(title=title,
-                    text=text, author=author,
-                    comment=comment, parent=None)
-
-        ok_(title in self.repo, "Document not in repository.")
-
-        self.repo.delete_page(title, author, comment)
-
-        ok_(title not in self.repo, "Document in repository after delete")
-
-    @raises(vstorage.DocumentNotFound)
-    def test_document_not_found(self):
-        self.repo.page_text(u'unknown entity')
-
-    def test_open_existing_repository(self):
-        self.repo.save_text(title=u'Python!', text=u'ham and spam')
-        current_repo_revision = self.repo.repo_revision()
-        same_repo = vstorage.VersionedStorage(self.repo_path)
-        assert_equal(same_repo.repo_revision(), current_repo_revision)
-
-    def test_history(self):
-        COMMITS = [
-            {"author": "bunny", "text":"1", "comment": "Oh yeah!"},
-            {"author": "frank", "text":"2", "comment": "Second is the best!"},
-            {"text":"3", "comment": "Third"},
-            {"author": "welma", "text":"4", "comment": "Fourth"},
-        ]
-
-        for commit in COMMITS:
-            self.repo.save_text(title=u"Sample", **commit)
-
-        for n, entry in enumerate(reversed(list(self.repo.page_history(u"Sample")))):
-            assert_equal(entry["version"], n)
-            assert_equal(entry["author"], COMMITS[n].get("author", "anonymous"))
-            assert_equal(entry["description"], COMMITS[n]["comment"])
-            assert_equal(entry["tag"], [])
-
-    def test_data_revert(self):
-        COMMITS = [
-            {u"title": u"one", "author": "bunny", "text":"1.1", "comment": "1"},
-            {u"title": u"one", "author": "frank", "text":"1.2", "comment": "2"},
-            {u"title": u"two", "author": "bunny", "text":"2.1", "comment": "3"},
-            {u"title": u"one", "author": "frank", "text":"1.3", "comment": "4"},
-        ]
-
-        for commit in COMMITS:
-            self.repo.save_text(**commit)
-
-        # now revert last change on one
-        self.repo.revert(u"one", 0)
-        assert_equal(self.repo.page_text(u"one"), (u"1.1", 3))
-        assert_equal(self.repo.page_text(u"two"), (u"2.1", 0))
-
-        self.repo.revert(u"one", 2)
-        assert_equal(self.repo.page_text(u"one"), (u"1.3", 4))
-
-class TestVSTags(object):
-
-    TITLE_1 = "Sample"
-
-    COMMITS = [
-        {"author": "bunny", "text":"1", "comment": "Oh yeah!"},
-        {"author": "frank", "text":"2", "comment": "Second is the best!"},
-        {"text":"3", "comment": "Third"},
-        {"author": "welma", "text":"4", "comment": "Fourth"},
-    ]
-
-    def setUp(self):
-        self.repo_path = tempfile.mkdtemp()
-        self.repo = vstorage.VersionedStorage(self.repo_path)
-
-        # generate some history
-        for commit in self.COMMITS:
-            self.repo.save_text(title=u"Sample", **commit)
-
-        # verify
-        for n, entry in enumerate(reversed(list(self.repo.page_history(self.TITLE_1)))):
-            assert_equal(entry["tag"], [])
-
-    def tearDown(self):
-        clear_directory(self.repo_path)
-
-    def test_add_tag(self):
-        TAG_USER = "mike_the_tagger"
-        TAG_NAME = "production"
-        TAG_VERSION = 2
-
-        # Add tag
-        self.repo.add_page_tag(self.TITLE_1, TAG_VERSION, TAG_NAME, TAG_USER)
-
-        # check history again
-        history = list(self.repo.page_history(self.TITLE_1))
-        for entry in reversed(history):
-            if entry["version"] == TAG_VERSION:
-                assert_equal(entry["tag"], [TAG_NAME])
-            else:
-                assert_equal(entry["tag"], [])
-
-    def test_add_many_tags(self):
-        TAG_USER = "mike_the_tagger"
-        tags = [
-            (2, "production", "mike"),
-            (2, "finished", "jeremy"),
-            (0, "original", "jeremy"),
-        ]
-
-        for rev, name, user in tags:
-            self.repo.add_page_tag(self.TITLE_1, rev, name, user)
-
-        # check history again
-        history = list(self.repo.page_history(self.TITLE_1))
-        for entry in reversed(history):
-            expected = [tag[1] for tag in tags if tag[0] == entry["version"]]
-            assert_equal(set(entry["tag"]), set(expected))
diff --git a/lib/wlapi/__init__.py b/lib/wlapi/__init__.py
deleted file mode 100644 (file)
index 3284211..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-"""
-   Abstraction over API for wolnelektury.pl
-"""
-import urllib2
-import functools
-import django.utils.simplejson as json
-import logging
-logger = logging.getLogger("fnp.lib.wlapi")
-
-
-class APICallException(Exception):
-
-    def __init__(self, cause=None):
-        super(Exception, self).__init__()
-        self.cause = cause
-
-    def __unicode__(self):
-        return u"%s, cause: %s" % (type(self).__name__, repr(self.cause))
-
-    def __str__(self):
-        return self.__unicode__().encode('utf-8')
-
-
-def api_call(path, format="json"):
-    def wrapper(func):
-
-        @functools.wraps(func)
-        def wrapped(self, *args, **kwargs):
-            generator = func(self, *args, **kwargs)
-
-            data = generator.next()
-
-            # prepare request
-            rq = urllib2.Request(self.base_url + path + ".json")
-
-            # will send POST when there is data, GET otherwise
-            if data is not None:
-                rq.add_data(json.dumps(data))
-                rq.add_header("Content-Type", "application/json")
-
-            try:
-                anwser = json.load(self.opener.open(rq))
-                return generator.send(anwser)
-            except StopIteration:
-                # by default, just return the anwser as a shorthand
-                return anwser
-            except urllib2.HTTPError, error:
-                return self._http_error(error)
-            except Exception, error:
-                return self._error(error)
-        return wrapped
-
-    return wrapper
-
-
-class WLAPI(object):
-
-    def __init__(self, **config_dict):
-        self.base_url = config_dict['URL']
-        self.auth_realm = config_dict['AUTH_REALM']
-        self.auth_user = config_dict['AUTH_USER']
-
-        digest_handler = urllib2.HTTPDigestAuthHandler()
-        digest_handler.add_password(
-                    realm=self.auth_realm, uri=self.base_url,
-                    user=self.auth_user, passwd=config_dict['AUTH_PASSWD'])
-
-        basic_handler = urllib2.HTTPBasicAuthHandler()
-        basic_handler.add_password(
-                    realm=self.auth_realm, uri=self.base_url,
-                    user=self.auth_user, passwd=config_dict['AUTH_PASSWD'])
-
-        self.opener = urllib2.build_opener(digest_handler, basic_handler)
-
-    def _http_error(self, error):
-        message = error.read()
-        logger.debug("HTTP ERROR: %s", message)
-        return self._error(message)
-
-    def _error(self, error):
-        raise APICallException(error)
-
-    @api_call("books")
-    def list_books(self):
-        yield
-
-    @api_call("books")
-    def publish_book(self, document):
-        yield {"text": document.text, "compressed": False}
diff --git a/lib/wlapi/tests.py b/lib/wlapi/tests.py
deleted file mode 100644 (file)
index a12ab06..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-
-from nose.tools import *
-from nose.core import runmodule
-
-import wlapi
-
-
-class FakeDocument():
-
-    def __init__(self):
-        self.text = "Some Text"
-
-
-class TestWLAPI(object):
-
-    def setUp(self):
-        self.api = wlapi.WLAPI(
-            URL="http://localhost:7000/api/",
-            AUTH_REALM="WL API",
-            AUTH_USER="platforma",
-            AUTH_PASSWD="platforma",
-        )
-
-    def test_basic_call(self):
-        assert_equal(self.api.list_books(), [])
-
-    def test_publish_book(self):
-        self.api.publish_book(FakeDocument())
-
-if __name__ == '__main__':
-    runmodule()
index 735743d..0c1b679 100644 (file)
@@ -86,7 +86,7 @@ MIDDLEWARE_CLASSES = (
 
 AUTHENTICATION_BACKENDS = (
     'django.contrib.auth.backends.ModelBackend',
-    'django_cas.backends.CASBackend',
+    'fnpdjango.auth_backends.AttrCASBackend',
 )
 
 ROOT_URLCONF = 'redakcja.urls'
@@ -116,6 +116,7 @@ INSTALLED_APPS = (
     'djkombu',
     'fileupload',
     'pipeline',
+    'fnpdjango',
 
     'catalogue',
     'cover',
index e741a1b..99d38f6 100644 (file)
@@ -13,6 +13,7 @@ httplib2 # oauth2 dependency
 
 ## Django
 Django>=1.5,<1.6
+fnpdjango<0.2
 django-pipeline>=1.2,<1.3
 django_cas>=2.1,<2.2
 sorl-thumbnail>=11.09,<12