Add Book.ancestor m2m.
[wolnelektury.git] / apps / catalogue / utils.py
index 1ac0ee8..df7e438 100644 (file)
@@ -4,6 +4,7 @@
 #
 from __future__ import with_statement
 
 #
 from __future__ import with_statement
 
+import hashlib
 import random
 import re
 import time
 import random
 import re
 import time
@@ -13,7 +14,7 @@ from django.http import HttpResponse
 from django.core.files.uploadedfile import UploadedFile
 from django.core.files.storage import DefaultStorage
 from django.utils.encoding import force_unicode
 from django.core.files.uploadedfile import UploadedFile
 from django.core.files.storage import DefaultStorage
 from django.utils.encoding import force_unicode
-from django.utils.hashcompat import sha_constructor
+from django.utils.translation import get_language
 from django.conf import settings
 from os import mkdir, path, unlink
 from errno import EEXIST, ENOENT
 from django.conf import settings
 from os import mkdir, path, unlink
 from errno import EEXIST, ENOENT
@@ -31,38 +32,23 @@ MAX_SESSION_KEY = 18446744073709551616L     # 2 << 63
 
 
 def get_random_hash(seed):
 
 
 def get_random_hash(seed):
-    sha_digest = sha_constructor('%s%s%s%s' %
+    sha_digest = hashlib.sha1('%s%s%s%s' %
         (randrange(0, MAX_SESSION_KEY), time.time(), unicode(seed).encode('utf-8', 'replace'),
         settings.SECRET_KEY)).digest()
     return urlsafe_b64encode(sha_digest).replace('=', '').replace('_', '-').lower()
 
 
         (randrange(0, MAX_SESSION_KEY), time.time(), unicode(seed).encode('utf-8', 'replace'),
         settings.SECRET_KEY)).digest()
     return urlsafe_b64encode(sha_digest).replace('=', '').replace('_', '-').lower()
 
 
-def split_tags(tags):
-    result = {}
+def split_tags(tags, initial=None):
+    if initial is None:
+        result = {}
+    else:
+        result = initial
+
     for tag in tags:
         result.setdefault(tag.category, []).append(tag)
     return result
 
 
     for tag in tags:
         result.setdefault(tag.category, []).append(tag)
     return result
 
 
-def get_dynamic_path(media, filename, ext=None, maxlen=100):
-    from slughifi import slughifi
-
-    # how to put related book's slug here?
-    if not ext:
-        # BookMedia case
-        ext = media.formats[media.type].ext
-    if media is None or not media.name:
-        name = slughifi(filename.split(".")[0])
-    else:
-        name = slughifi(media.name)
-    return 'book/%s/%s.%s' % (ext, name[:maxlen-len('book/%s/.%s' % (ext, ext))-4], ext)
-
-
-# TODO: why is this hard-coded ?
-def book_upload_path(ext=None, maxlen=100):
-    return lambda *args: get_dynamic_path(*args, ext=ext, maxlen=maxlen)
-
-
 class ExistingFile(UploadedFile):
 
     def __init__(self, path, *args, **kwargs):
 class ExistingFile(UploadedFile):
 
     def __init__(self, path, *args, **kwargs):
@@ -144,7 +130,7 @@ def remove_zip(zip_slug):
 class AttachmentHttpResponse(HttpResponse):
     """Response serving a file to be downloaded.
     """
 class AttachmentHttpResponse(HttpResponse):
     """Response serving a file to be downloaded.
     """
-    def __init__ (self, file_path, file_name, mimetype):
+    def __init__(self, file_path, file_name, mimetype):
         super(AttachmentHttpResponse, self).__init__(mimetype=mimetype)
         self['Content-Disposition'] = 'attachment; filename=%s' % file_name
         self.file_path = file_path
         super(AttachmentHttpResponse, self).__init__(mimetype=mimetype)
         self['Content-Disposition'] = 'attachment; filename=%s' % file_name
         self.file_path = file_path
@@ -158,15 +144,15 @@ class MultiQuerySet(object):
     def __init__(self, *args, **kwargs):
         self.querysets = args
         self._count = None
     def __init__(self, *args, **kwargs):
         self.querysets = args
         self._count = None
-    
+
     def count(self):
         if not self._count:
             self._count = sum(len(qs) for qs in self.querysets)
         return self._count
     def count(self):
         if not self._count:
             self._count = sum(len(qs) for qs in self.querysets)
         return self._count
-    
+
     def __len__(self):
         return self.count()
     def __len__(self):
         return self.count()
-        
+
     def __getitem__(self, item):
         try:
             indices = (offset, stop, step) = item.indices(self.count())
     def __getitem__(self, item):
         try:
             indices = (offset, stop, step) = item.indices(self.count())
@@ -187,6 +173,55 @@ class MultiQuerySet(object):
                     stop = total_len - len(items)
                     continue
 
                     stop = total_len - len(items)
                     continue
 
+class SortedMultiQuerySet(MultiQuerySet):
+    def __init__(self, *args, **kwargs):
+        self.order_by = kwargs.pop('order_by', None)
+        self.sortfn = kwargs.pop('sortfn', None)
+        if self.order_by is not None:
+            self.sortfn = lambda a, b: cmp((getattr(a, f) for f in self.order_by),
+                                           (getattr(b, f) for f in self.order_by))
+        super(SortedMultiQuerySet, self).__init__(*args, **kwargs)
+
+    def __getitem__(self, item):
+        sort_heads = [0] * len(self.querysets)
+        try:
+            indices = (offset, stop, step) = item.indices(self.count())
+        except AttributeError:
+            # it's not a slice - make it one
+            return self[item : item + 1][0]
+        items = []
+        total_len = stop - offset
+        skipped = 0
+        i_s = range(len(sort_heads))
+
+        while len(items) < total_len:
+            candidate = None
+            candidate_i = None
+            for i in i_s:
+                def get_next():
+                    return self.querysets[i][sort_heads[i]]
+                try:
+                    if candidate is None:
+                        candidate = get_next()
+                        candidate_i = i
+                    else:
+                        competitor = get_next()
+                        if self.sortfn(candidate, competitor) > 0:
+                            candidate = competitor
+                            candidate_i = i
+                except IndexError:
+                    continue # continue next sort_head
+            # we have no more elements:
+            if candidate is None:
+                break
+            sort_heads[candidate_i] += 1
+            if skipped < offset:
+                skipped += 1
+                continue # continue next item
+            items.append(candidate)
+
+        return items
+
 
 def truncate_html_words(s, num, end_text='...'):
     """Truncates HTML to a certain number of words (not counting tags and
 
 def truncate_html_words(s, num, end_text='...'):
     """Truncates HTML to a certain number of words (not counting tags and
@@ -300,3 +335,19 @@ class AppSettings(object):
         if hasattr(self, more):
             value = getattr(self, more)(value)
         return value
         if hasattr(self, more):
             value = getattr(self, more)(value)
         return value
+
+
+def trim_query_log(trim_to=25):
+    """
+connection.queries includes all SQL statements -- INSERTs, UPDATES, SELECTs, etc. Each time your app hits the database, the query will be recorded.
+This can sometimes occupy lots of memory, so trim it here a bit.
+    """
+    if settings.DEBUG:
+        from django.db import connection
+        connection.queries = trim_to > 0 \
+            and connection.queries[-trim_to:] \
+            or []
+
+
+def delete_from_cache_by_language(cache, key_template):
+    cache.delete_many([key_template % lc for lc, ln in settings.LANGUAGES])