def get_absolute_url(self):
return ('catalogue.views.book_detail', [self.slug])
+ @staticmethod
+ @permalink
+ def create_url(slug):
+ return ('catalogue.views.book_detail', [slug])
+
@property
def name(self):
return self.title
def pretty_title(self, html_links=False):
book = self
- names = list(book.tags.filter(category='author'))
-
- books = []
- while book:
- books.append(book)
- book = book.parent
- names.extend(reversed(books))
+ rel_info = book.related_info()
+ names = [(name, Tag.create_url('author', slug))
+ for name, slug in rel_info['tags']['author']]
+ if 'parents' in rel_info:
+ books = [(name, Book.create_url(slug))
+ for name, slug in rel_info['parents']]
+ names.extend(reversed(books))
+ names.append((self.title, self.get_absolute_url()))
if html_links:
- names = ['<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name) for tag in names]
+ names = ['<a href="%s">%s</a>' % (tag[1], tag[0]) for tag in names]
else:
- names = [tag.name for tag in names]
-
+ names = [tag[0] for tag in names]
return ', '.join(names)
@classmethod
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
from django.conf import settings
+from django.core.cache import get_cache
from django.db.models.signals import post_save, pre_delete, post_delete
import django.dispatch
-from catalogue.models import Tag, BookMedia, Book, Fragment
+from catalogue.models import Tag, BookMedia, Book, Fragment, Collection
from catalogue import tasks
from newtagging.models import tags_updated
+permanent_cache = get_cache('permanent')
+
+
def _tags_updated_handler(sender, affected_tags, **kwargs):
# reset tag global counter
# we want Tag.changed_at updated for API to know the tag was touched
""" refresh all the short_html stuff on BookMedia update """
if sender == BookMedia:
instance.book.save()
+ permanent_cache.delete_many([
+ 'catalogue.audiobook_list', 'catalogue.daisy_list'])
+ elif sender == Collection:
+ permanent_cache.delete('catalogue.collection:%s' % instance.slug)
post_save.connect(_post_save_handler)
+def post_publish(sender, **kwargs):
+ permanent_cache.delete('catalogue.book_list')
+Book.published.connect(post_publish)
+
+
if not settings.NO_SEARCH_INDEX:
@django.dispatch.receiver(post_delete, sender=Book)
def _remove_book_from_index_handler(sender, instance, **kwargs):
def get_absolute_url(self):
return ('catalogue.views.tagged_object_list', [self.url_chunk])
+ @classmethod
+ @permalink
+ def create_url(cls, category, slug):
+ return ('catalogue.views.tagged_object_list', [
+ '/'.join((cls.categories_dict[category], slug))
+ ])
+
def has_description(self):
return len(self.description) > 0
has_description.short_description = _('description')
Możecie z niej korzystać bezpłatnie i bez ograniczeń.
Audiobooki nagrywają znani aktorzy, wśród nich Danuta Stenka i Jan Peszek.{% endblocktrans %}</p>
{% endblock %}
-
-
-{% block book_list %}
- {% audiobook_tree orphans books_by_parent %}
- {% for author, group in books_by_author.items %}
- {% if group %}
- <a name="{{ author.slug }}"></a>
- <div class="group">
- <h2><a href="{{ author.get_absolute_url }}">{{ author }}</a></h2>
- {% audiobook_tree group books_by_parent %}
- </div>
- {% endif %}
- {% endfor %}
-{% endblock %}
{% extends "base.html" %}
{% load i18n %}
-{% load catalogue_tags chunks %}
+{% load catalogue_tags %}
{% block bodyid %}book-a-list{% endblock %}
<div id="book-list-nav" class="normal-text">
{% trans "Table of Content" %}
- {% for index, authors in books_nav.items %}
- <ul>
- <li><a class="book-list-index" href="#">{{ index|upper }}</a></li>
- <ul class="book-list-show-index">
- {% for author in authors %}
- <li><a href="#{{ author.slug }}">{{ author }}</a></li>
- {% endfor %}
- </ul>
- </ul>
- {% endfor %}
+ {{ rendered_nav }}
</div>
<div id="book-list" class="normal-text">
{% block book_list %}
- {% book_tree orphans books_by_parent %}
- {% for author, group in books_by_author.items %}
- {% if group %}
- <a name="{{ author.slug }}"></a>
- <div class="group">
- <h2><a href="{{ author.get_absolute_url }}">{{ author }}</a></h2>
- {% book_tree group books_by_parent %}
- </div>
- {% endif %}
- {% endfor %}
+ {{ rendered_book_list }}
{% endblock %}
</div>
<div id="book-list-up">
-{% load i18n %}
-{% load tag_list from catalogue_tags %}
+{% load i18n static %}
<ul id="menu">
+ {% for category, name, hash in categories %}
<li class="hidden-box-wrapper menu">
- <a href="{% url catalogue %}#autorzy" class="hidden-box-trigger menu">
- <span class='mono'>{% trans "Authors" %}</span></a>
- <div class="hidden-box">{% if author %}{% tag_list author %}{% endif %}</div>
- </li>
- <li class="hidden-box-wrapper menu">
- <a href="{% url catalogue %}#gatunki" class="hidden-box-trigger menu">
- <span class='mono'>{% trans "Genres" %}</span></a>
- <div class="hidden-box">{% if genre %}{% tag_list genre %}{% endif %}</div>
- </li>
- <li class="hidden-box-wrapper menu">
- <a href="{% url catalogue %}#rodzaje" class="hidden-box-trigger menu">
- <span class='mono'>{% trans "Kinds" %}</span></a>
- <div class="hidden-box">{% if kind %}{% tag_list kind %}{% endif %}</div>
- </li>
- <li class="hidden-box-wrapper menu">
- <a href="{% url catalogue %}#epoki" class="hidden-box-trigger menu">
- <span class='mono'>{% trans "Epochs" %}</span></a>
- <div class="hidden-box">{% if epoch %}{% tag_list epoch %}{% endif %}</div>
- </li>
- <li class="hidden-box-wrapper menu">
- <a href="{% url catalogue %}#motywy" class="hidden-box-trigger menu">
- <span class='mono'>{% trans "Themes" %}</span></a>
- <div class="hidden-box">{% if theme %}{% tag_list theme %}{% endif %}</div>
+ <a href="{% url catalogue %}#{{ hash }}" class="hidden-box-trigger menu load-menu">
+ <span class='mono'>{{ name }}</span></a>
+ <div class="hidden-box" id="menu-{{ category }}">
+ <img src="{% static "img/indicator.gif" %}" alt="{% trans "Please wait…" %}" />
+ </div>
</li>
+ {% endfor %}
+
<li class="menu">
<a href="{% url book_list %}" class="menu">
<span class='mono'>{% trans "All books" %}</span></a>
--- /dev/null
+{% load catalogue_tags %}
+
+{% audiobook_tree orphans books_by_parent %}
+{% for author, group in books_by_author.items %}
+ {% if group %}
+ <a name="{{ author.slug }}"></a>
+ <div class="group">
+ <h2><a href="{{ author.get_absolute_url }}">{{ author }}</a></h2>
+ {% audiobook_tree group books_by_parent %}
+ </div>
+ {% endif %}
+{% endfor %}
--- /dev/null
+{% load catalogue_tags %}
+
+{% book_tree orphans books_by_parent %}
+{% for author, group in books_by_author.items %}
+ {% if group %}
+ <a name="{{ author.slug }}"></a>
+ <div class="group">
+ <h2><a href="{{ author.get_absolute_url }}">{{ author }}</a></h2>
+ {% book_tree group books_by_parent %}
+ </div>
+ {% endif %}
+{% endfor %}
--- /dev/null
+{% for index, authors in books_nav.items %}
+ <ul>
+ <li><a class="book-list-index" href="#">{{ index|upper }}</a></li>
+ <ul class="book-list-show-index">
+ {% for author in authors %}
+ <li><a href="#{{ author.slug }}">{{ author }}</a></li>
+ {% endfor %}
+ </ul>
+ </ul>
+{% endfor %}
#
import datetime
import feedparser
+from random import randint
from django.conf import settings
from django import template
ignore_by_tag=book.book_tag())[:limit-random]
cache.set(cache_key, related, 1800)
if random:
- related += list(Book.objects.exclude(
- pk__in=[b.pk for b in related] + [book.pk]
- ).order_by('?')[:random])
+ random_books = Book.objects.exclude(
+ pk__in=[b.pk for b in related] + [book.pk])
+ if random == 1:
+ count = random_books.count()
+ if count:
+ related.append(random_books[randint(0, count - 1)])
+ else:
+ related += list(random_books.order_by('?')[:random])
return {
'books': related,
}
@register.inclusion_tag('catalogue/menu.html')
def catalogue_menu():
- tags = Tag.objects.filter(
- category__in=('author', 'epoch', 'genre', 'kind', 'theme')
- ).exclude(book_count=0)
- return split_tags(tags)
-
+ return {'categories': [
+ ('author', _('Authors'), 'autorzy'),
+ ('genre', _('Genres'), 'gatunki'),
+ ('kind', _('Kinds'), 'rodzaje'),
+ ('epoch', _('Epochs'), 'epoki'),
+ ('theme', _('Themes'), 'autorzy'),
+ ]}
@register.simple_tag
def tag_url(category, slug):
- return reverse('catalogue.views.tagged_object_list', args=[
- '/'.join((Tag.categories_dict[category], slug))
- ])
+ return Tag.create_url(category, slug)
@register.simple_tag
import itertools
from django.conf import settings
+from django.core.cache import get_cache
from django.template import RequestContext
+from django.template.loader import render_to_string
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse
from catalogue import models
from catalogue import forms
from catalogue.utils import split_tags, MultiQuerySet
+from catalogue.templatetags.catalogue_tags import tag_list
from pdcounter import models as pdcounter_models
from pdcounter import views as pdcounter_views
from suggest.forms import PublishingSuggestForm
from picture.models import Picture
staff_required = user_passes_test(lambda user: user.is_staff)
+permanent_cache = get_cache('permanent')
def catalogue(request):
categories = split_tags(tags)
fragment_tags = categories.get('theme', [])
- return render_to_response('catalogue/catalogue.html', locals(),
- context_instance=RequestContext(request))
+ if request.is_ajax():
+ render_tag_list = lambda x: render_to_string(
+ 'catalogue/tag_list.html', tag_list(x))
+ output = {'theme': render_tag_list(fragment_tags)}
+ for category, tags in categories.items():
+ output[category] = render_tag_list(tags)
+ return JSONResponse(output)
+ else:
+ return render_to_response('catalogue/catalogue.html', locals(),
+ context_instance=RequestContext(request))
def book_list(request, filter=None, template_name='catalogue/book_list.html',
- context=None):
+ nav_template_name='catalogue/snippets/book_list_nav.html',
+ list_template_name='catalogue/snippets/book_list.html',
+ cache_key='catalogue.book_list',
+ context=None,
+ ):
""" generates a listing of all books, optionally filtered with a test function """
-
- books_by_author, orphans, books_by_parent = models.Book.book_list(filter)
- books_nav = SortedDict()
- for tag in books_by_author:
- if books_by_author[tag]:
- books_nav.setdefault(tag.sort_key[0], []).append(tag)
-
+ cached = permanent_cache.get(cache_key)
+ if cached is not None:
+ rendered_nav, rendered_book_list = cached
+ else:
+ books_by_author, orphans, books_by_parent = models.Book.book_list(filter)
+ books_nav = SortedDict()
+ for tag in books_by_author:
+ if books_by_author[tag]:
+ books_nav.setdefault(tag.sort_key[0], []).append(tag)
+ rendered_nav = render_to_string(nav_template_name, locals())
+ rendered_book_list = render_to_string(list_template_name, locals())
+ permanent_cache.set(cache_key, (rendered_nav, rendered_book_list))
return render_to_response(template_name, locals(),
context_instance=RequestContext(request))
def audiobook_list(request):
return book_list(request, Q(media__type='mp3') | Q(media__type='ogg'),
- template_name='catalogue/audiobook_list.html')
+ template_name='catalogue/audiobook_list.html',
+ list_template_name='catalogue/snippets/audiobook_list.html',
+ cache_key='catalogue.audiobook_list')
def daisy_list(request):
return book_list(request, Q(media__type='daisy'),
- template_name='catalogue/daisy_list.html')
+ template_name='catalogue/daisy_list.html',
+ cache_key='catalogue.daisy_list')
def collection(request, slug):
for slug in slugs]
return book_list(request, Q(slug__in=slugs),
template_name='catalogue/collection.html',
+ cache_key='catalogue.collection:%s' % coll.slug,
context={'collection': coll})
# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
+from random import randint
from django import template
from catalogue.models import Book
from social.models import Cite
cites = cites_for_tags([ctx.book_tag()])
else:
cites = cites_for_tags(ctx)
- cite = cites.order_by('-sticky', '?')[0] if cites.exists() else None
+ stickies = cites.filter(sticky=True)
+ count = stickies.count()
+ if count:
+ cite = stickies[randint(0, count - 1)]
+ else:
+ count = cites.count()
+ if count:
+ cite = cites[randint(0, count - 1)]
+ else:
+ cite = None
return cite
(function() {
var $current = null;
+ var menu_loaded = false;
$('.hidden-box-wrapper').each(function() {
var $hidden = $('.hidden-box', this);
$('.hidden-box-trigger', this).click(function(event) {
$current && $current.hide('fast');
$hidden.show('fast');
$current = $hidden;
+ if ($(this).hasClass('load-menu') && !menu_loaded) {
+ $.ajax({
+ url: '/katalog/',
+ dataType: "json",
+ }).done(function(data) {
+ $.each(data, function(index, value) {
+ $('#menu-' + index).html(value);
+ });
+ menu_loaded = true;
+ });
+ }
}
});
});
<div id="main-content">
<div id="nav-line">
- {% cache 60 catalogue-menu LANGUAGE_CODE %}
- {% catalogue_menu %}
- {% endcache %}
+ {% catalogue_menu %}
<div id="lang-menu" class="hoverget">
<span id='lang-button' class='mono hoverclick'>
from __future__ import with_statement # needed for python 2.5
from fabric.api import *
from fabric.contrib import files
+from fabric.context_managers import path
import os
env.python = '/usr/bin/python'
env.virtualenv = '/usr/bin/virtualenv'
env.pip = 've/bin/pip'
+ env.restart_webserver = restart_gunicorn_debian
# =========
# = Tasks =
migrate()
collectstatic()
restart_webserver()
+ restart_celery()
def deploy_version(version):
"Specify a specific version to be made live"
run('rm releases/previous; mv releases/current releases/previous;', pty=True)
run('ln -s %(version)s releases/current' % env, pty=True)
restart_webserver()
+ restart_celery()
def rollback():
"""
run('mv releases/previous releases/current;', pty=True)
run('mv releases/_previous releases/previous;', pty=True)
restart_webserver()
+ restart_celery()
# =====================================================================
with cd('%(path)s/releases/current/%(project_name)s' % env):
run('../../../ve/bin/python manage.py collectstatic --noinput' % env, pty=True)
+def restart_gunicorn_debian():
+ """Restarts gunicorn server using debian script."""
+ print '>>> restart gunicorn'
+ require('project_name', provided_by=[staging, production])
+ with path('/sbin'):
+ sudo('gunicorn-debian restart %(project_name)s' % env, shell=False)
+
def restart_webserver():
- "Restart the web server"
- print '>>> restart webserver'
- run('touch %(path)s/%(project_name)s.wsgi' % env)
+ """Restarts the web server."""
+ if hasattr(env, 'restart_webserver'):
+ env.restart_webserver()
+ else:
+ require('project_name', provided_by=[staging, production])
+ print '>>> restart webserver'
+ run('touch %(path)s/%(project_name)s.wsgi' % env)
+
+def restart_celery():
+ """Restarts the Celery task queue manager."""
print '>>> restart Celery'
+ require('project_name', provided_by=[staging, production])
sudo('supervisorctl restart celery.%(project_name)s:' % env, shell=False)
-Subproject commit 1ffcf32a8d795681f54bd125ca45e908da5ed3fb
+Subproject commit 3754989331c91f1d78cd5c1904f768a4cf80f07a
--- /dev/null
+class SetRemoteAddrFromXRealIP(object):
+ """Sets REMOTE_ADDR from the X-Real-IP header, as set by Nginx."""
+ def process_request(self, request):
+ try:
+ request.META['REMOTE_ADDR'] = request.META['HTTP_X_REAL_IP']
+ except KeyError:
+ return None
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
+ 'realip_middleware.SetRemoteAddrFromXRealIP',
]
ROOT_URLCONF = 'wolnelektury.urls'
--- /dev/null
+import os
+import os.path
+import sys
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# Add apps and lib directories to PYTHONPATH
+sys.path = [
+ ROOT,
+ os.path.join(ROOT, 'wolnelektury'),
+ os.path.join(ROOT, 'apps'),
+ os.path.join(ROOT, 'lib'),
+ os.path.join(ROOT, 'lib/librarian'),
+] + sys.path
+
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
+
+# This application object is used by the development server
+# as well as any WSGI server configured to use this file.
+from django.core.wsgi import get_wsgi_application
+application = get_wsgi_application()