keep descendants on shelves
[wolnelektury.git] / apps / catalogue / views.py
index 4d8ee7e..102b38e 100644 (file)
@@ -9,7 +9,7 @@ import pprint
 import traceback
 import re
 import itertools
-from operator import itemgetter 
+from operator import itemgetter
 
 from django.conf import settings
 from django.template import RequestContext
@@ -73,22 +73,41 @@ def book_list(request):
         context_instance=RequestContext(request))
 
 
-def tagged_object_list(request, tags=''):
-    # Prevent DoS attacks on our database
-    if len(tags.split('/')) > 6:
-        raise Http404
+def differentiate_tags(request, tags, ambiguous_slugs):
+    beginning = '/'.join(tag.url_chunk for tag in tags)
+    unparsed = '/'.join(ambiguous_slugs[1:])
+    options = []
+    for tag in models.Tag.objects.exclude(category='book').filter(slug=ambiguous_slugs[0]):
+        options.append({
+            'url_args': '/'.join((beginning, tag.url_chunk, unparsed)).strip('/'),
+            'tags': [tag]
+        })
+    return render_to_response('catalogue/differentiate_tags.html',
+                {'tags': tags, 'options': options, 'unparsed': ambiguous_slugs[1:]},
+                context_instance=RequestContext(request))
 
+
+def tagged_object_list(request, tags=''):
     try:
         tags = models.Tag.get_tag_list(tags)
     except models.Tag.DoesNotExist:
         raise Http404
+    except models.Tag.MultipleObjectsReturned, e:
+        return differentiate_tags(request, e.tags, e.ambiguous_slugs)
+
+    try:
+        if len(tags) > settings.MAX_TAG_LIST:
+            raise Http404
+    except AttributeError:
+        pass
 
     if len([tag for tag in tags if tag.category == 'book']):
         raise Http404
 
     theme_is_set = [tag for tag in tags if tag.category == 'theme']
-    shelf_is_set = len(tags) == 1 and tags[0].category == 'set'
-    my_shelf_is_set = shelf_is_set and request.user.is_authenticated() and request.user == tags[0].user
+    shelf_is_set = [tag for tag in tags if tag.category == 'set']
+    only_shelf = shelf_is_set and len(tags) == 1
+    only_my_shelf = only_shelf and request.user.is_authenticated() and request.user == tags[0].user
 
     objects = only_author = pd_counter = None
     categories = {}
@@ -104,7 +123,7 @@ def tagged_object_list(request, tags=''):
             fragments = models.Fragment.tagged.with_any(l_tags, fragments)
 
         # newtagging goes crazy if we just try:
-        #related_tags = models.Tag.objects.usage_for_queryset(fragments, counts=True, 
+        #related_tags = models.Tag.objects.usage_for_queryset(fragments, counts=True,
         #                    extra={'where': ["catalogue_tag.category != 'book'"]})
         fragment_keys = [fragment.pk for fragment in fragments]
         if fragment_keys:
@@ -118,12 +137,13 @@ def tagged_object_list(request, tags=''):
     else:
         # get relevant books and their tags
         objects = models.Book.tagged.with_all(tags).order_by()
-        l_tags = [book.book_tag() for book in objects]
-        # eliminate descendants
-        descendants_keys = [book.pk for book in models.Book.tagged.with_any(l_tags)]
-        if descendants_keys:
-            objects = objects.exclude(pk__in=descendants_keys)
-        
+        if not shelf_is_set:
+            # eliminate descendants
+            l_tags = [book.book_tag() for book in objects]
+            descendants_keys = [book.pk for book in models.Book.tagged.with_any(l_tags)]
+            if descendants_keys:
+                objects = objects.exclude(pk__in=descendants_keys)
+
         # get related tags from `tag_counter` and `theme_counter`
         related_counts = {}
         tags_pks = [tag.pk for tag in tags]
@@ -136,7 +156,7 @@ def tagged_object_list(request, tags=''):
         related_tags = [tag for tag in related_tags if tag not in tags]
         for tag in related_tags:
             tag.count = related_counts[tag.pk]
-        
+
         categories = split_tags(related_tags)
         del related_tags
 
@@ -151,10 +171,10 @@ def tagged_object_list(request, tags=''):
         template_name='catalogue/tagged_object_list.html',
         extra_context={
             'categories': categories,
-            'shelf_is_set': shelf_is_set,
+            'only_shelf': only_shelf,
             'only_author': only_author,
             'pd_counter': pd_counter,
-            'user_is_owner': my_shelf_is_set,
+            'only_my_shelf': only_my_shelf,
             'formats_form': forms.DownloadFormatsForm(),
 
             'tags': tags,
@@ -164,8 +184,8 @@ def tagged_object_list(request, tags=''):
 
 def book_fragments(request, book_slug, theme_slug):
     book = get_object_or_404(models.Book, slug=book_slug)
-    book_tag = get_object_or_404(models.Tag, slug='l-' + book_slug)
-    theme = get_object_or_404(models.Tag, slug=theme_slug)
+    book_tag = get_object_or_404(models.Tag, slug='l-' + book_slug, category='book')
+    theme = get_object_or_404(models.Tag, slug=theme_slug, category='theme')
     fragments = models.Fragment.tagged.with_all([book_tag, theme])
 
     form = forms.SearchForm()
@@ -220,7 +240,7 @@ def book_text(request, slug):
 
 def _no_diacritics_regexp(query):
     """ returns a regexp for searching for a query without diacritics
-    
+
     should be locale-aware """
     names = {
         u'a':u'aąĄ', u'c':u'cćĆ', u'e':u'eęĘ', u'l': u'lłŁ', u'n':u'nńŃ', u'o':u'oóÓ', u's':u'sśŚ', u'z':u'zźżŹŻ',
@@ -238,16 +258,16 @@ def unicode_re_escape(query):
 def _word_starts_with(name, prefix):
     """returns a Q object getting models having `name` contain a word
     starting with `prefix`
-    
+
     We define word characters as alphanumeric and underscore, like in JS.
-    
+
     Works for MySQL, PostgreSQL, Oracle.
     For SQLite, _sqlite* version is substituted for this.
     """
     kwargs = {}
 
     prefix = _no_diacritics_regexp(unicode_re_escape(prefix))
-    # can't use [[:<:]] (word start), 
+    # can't use [[:<:]] (word start),
     # but we want both `xy` and `(xy` to catch `(xyz)`
     kwargs['%s__iregex' % name] = u"(^|[^[:alnum:]_])%s" % prefix
 
@@ -255,8 +275,8 @@ def _word_starts_with(name, prefix):
 
 
 def _sqlite_word_starts_with(name, prefix):
-    """ version of _word_starts_with for SQLite 
-    
+    """ version of _word_starts_with for SQLite
+
     SQLite in Django uses Python re module
     """
     kwargs = {}
@@ -288,7 +308,7 @@ def _get_result_link(match, tag_list):
         return match.get_absolute_url()
     else:
         return reverse('catalogue.views.tagged_object_list',
-            kwargs={'tags': '/'.join(tag.slug for tag in tag_list + [match])}
+            kwargs={'tags': '/'.join(tag.url_chunk for tag in tag_list + [match])}
         )
 
 def _get_result_type(match):
@@ -302,12 +322,12 @@ def _get_result_type(match):
 
 def find_best_matches(query, user=None):
     """ Finds a Book, Tag or Bookstub best matching a query.
-    
+
     Returns a with:
       - zero elements when nothing is found,
       - one element when a best result is found,
       - more then one element on multiple exact matches
-    
+
     Raises a ValueError on too short a query.
     """
 
@@ -357,7 +377,6 @@ def tags_starting_with(request):
 
     return HttpResponse('\n'.join(tag.name for tag in _tags_starting_with(prefix, request.user)))
 
-
 # ====================
 # = Shelf management =
 # ====================
@@ -441,7 +460,7 @@ def download_shelf(request, slug):
     """"
     Create a ZIP archive on disk and transmit it in chunks of 8KB,
     without loading the whole file into memory. A similar approach can
-    be used for large dynamic PDF files.                                        
+    be used for large dynamic PDF files.
     """
     shelf = get_object_or_404(models.Tag, slug=slug, category='set')