- License: [MIT License](http://www.opensource.org/licenses/mit-license.php)
- Type: library (Django application)
- django-chunks
+django-chunks
-------------
- Source: [Google Code](http://code.google.com/p/django-chunks/)
- Authors: Clint Ecker <clintecker@gmail.com>
- Type: library (Django aplication)
- Notes: Aplication based on [django-tagging](http://code.google.com/p/django-tagging/), also [MIT](http://www.opensource.org/licenses/mit-license.php) license.
-django-piston (0.2.3rc)
-------------------------
- - http://bitbucket.org/jespern/django-piston/wiki/Home
-markupstring
-------------
- - Source: [ASPN Cookbook](http://code.activestate.com/recipes/389023/)
- - Authors: Thomas Hinkle
- - License: [MIT License](http://code.activestate.com/help/terms/)
- - Type: library
- - Notes: Patched by Marek Stępniowski <marek@stepniowski.com> to accept Unicode strings
-
-
Authors
=======
"pk": "promo",
"model": "chunks.chunk",
"fields": {
- "content": "<h2>Biblioteczka Boya</h2>\r\n\r\n<p>T\u0142umaczenia literatury francuskiej i nie tylko.</p>\r\n\r\n<p class=\"see-more\"><a href=\"/katalog/lektury/boy/\" >Biblioteczka Boya \u21d2</a></p>",
+ "content": "<div class=\"grid-line\" id=\"promo-box-header\"><h2 class=\"mono\">\r\n\r\n\r\nOh Boy!\r\n\r\n\r\n</h2></div><div id=\"promo-box-body\" class=\"accent4\"><p id=\"promo-box-title\" class=\"mono\"><span>\r\n\r\n\r\nBiblioteczka Boya\r\n\r\n\r\n</span></p><div id=\"promo-box-content\">\r\n\r\n\r\n<p>T\u0142umaczenia literatury francuskiej i nie tylko.</p>\r\n\r\n<p class=\"see-more\"><a href=\"/katalog/lektury/boy/\" >Biblioteczka Boya \u21d2</a></p>\r\n\r\n\r\n</div></div>",
"description": "boks promocyjny na g\u0142\u00f3wnej"
}
},
from newtagging.models import TagBase, tags_updated
from newtagging import managers
from catalogue.fields import JSONField, OverwritingFileField
-from catalogue.utils import create_zip, split_tags
+from catalogue.utils import create_zip, split_tags, truncate_html_words
from catalogue.tasks import touch_tag, index_book
from shutil import copy
from glob import glob
def build_html(self):
- from markupstring import MarkupString
from django.core.files.base import ContentFile
from slughifi import slughifi
from librarian import html
continue
text = fragment.to_string()
- short_text = ''
- markup = MarkupString(text)
- if (len(markup) > 240):
- short_text = unicode(markup[:160])
+ short_text = truncate_html_words(text, 15)
+ if text == short_text:
+ short_text = ''
new_fragment = Fragment.objects.create(anchor=fragment.id, book=self,
text=text, short_text=short_text)
for lang, langname in settings.LANGUAGES:
permanent_cache.delete(cache_key % (self.id, lang))
+ def get_short_text(self):
+ """Returns short version of the fragment."""
+ return self.short_text if self.short_text else self.text
+
def short_html(self):
if self.id:
cache_key = "Fragment.short_html/%d/%s" % (self.id, get_language())
return reverse('catalogue.views.tagged_object_list', args=[
'/'.join((Tag.categories_dict[category], slug))
])
-
-
-@register.filter
-@stringfilter
-def removewholetags(value, tags):
- """Removes a space separated list of [X]HTML tags from the output.
-
- FIXME: It makes the assumption the removed tags aren't nested.
-
- """
- tags = [re.escape(tag) for tag in tags.split()]
- tags_re = u'(%s)' % u'|'.join(tags)
- tag_re = re.compile(ur'<%s[^>]*>.*?</\s*\1\s*>' % tags_re, re.U)
- value = tag_re.sub(u'', value)
- return value
from __future__ import with_statement
import random
+import re
import time
from base64 import urlsafe_b64encode
from django.core.files.uploadedfile import UploadedFile
from django.core.files.base import File
from django.core.files.storage import DefaultStorage
+from django.utils.encoding import force_unicode
from django.utils.hashcompat import sha_constructor
from django.conf import settings
from celery.task import task
def __init__(self, path, *args, **kwargs):
self.path = path
- return super(ExistingFile, self).__init__(*args, **kwargs)
+ super(ExistingFile, self).__init__(*args, **kwargs)
def temporary_file_path(self):
return self.path
offset = 0
stop = total_len - len(items)
continue
+
+
+def truncate_html_words(s, num, end_text='...'):
+ """Truncates HTML to a certain number of words (not counting tags and
+ comments). Closes opened tags if they were correctly closed in the given
+ html. Takes an optional argument of what should be used to notify that the
+ string has been truncated, defaulting to ellipsis (...).
+
+ Newlines in the HTML are preserved.
+
+ This is just a version of django.utils.text.truncate_html_words with no space before the end_text.
+ """
+ s = force_unicode(s)
+ length = int(num)
+ if length <= 0:
+ return u''
+ html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input')
+ # Set up regular expressions
+ re_words = re.compile(r'&.*?;|<.*?>|(\w[\w-]*)', re.U)
+ re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>')
+ # Count non-HTML words and keep note of open tags
+ pos = 0
+ end_text_pos = 0
+ words = 0
+ open_tags = []
+ while words <= length:
+ m = re_words.search(s, pos)
+ if not m:
+ # Checked through whole string
+ break
+ pos = m.end(0)
+ if m.group(1):
+ # It's an actual non-HTML word
+ words += 1
+ if words == length:
+ end_text_pos = pos
+ continue
+ # Check for tag
+ tag = re_tag.match(m.group(0))
+ if not tag or end_text_pos:
+ # Don't worry about non tags or tags after our truncate point
+ continue
+ closing_tag, tagname, self_closing = tag.groups()
+ tagname = tagname.lower() # Element names are always case-insensitive
+ if self_closing or tagname in html4_singlets:
+ pass
+ elif closing_tag:
+ # Check for match in open tags list
+ try:
+ i = open_tags.index(tagname)
+ except ValueError:
+ pass
+ else:
+ # SGML: An end tag closes, back to the matching start tag, all unclosed intervening start tags with omitted end tags
+ open_tags = open_tags[i+1:]
+ else:
+ # Add it to the start of the open tags list
+ open_tags.insert(0, tagname)
+ if words <= length:
+ # Don't try to close tags if we don't need to truncate
+ return s
+ out = s[:end_text_pos]
+ if end_text:
+ out += end_text
+ # Close any tags still open
+ for tag in open_tags:
+ out += '</%s>' % tag
+ # Return string
+ return out
#
import re
import itertools
-from datetime import datetime
from django.conf import settings
from django.template import RequestContext
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse
-from django.db.models import Count, Sum, Q
+from django.db.models import Q
from django.contrib.auth.decorators import login_required, user_passes_test
from django.utils.datastructures import SortedDict
-from django.views.decorators.http import require_POST
-from django.contrib import auth
-from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.utils.http import urlquote_plus
-from django.views.decorators import cache
from django.utils import translation
from django.utils.translation import ugettext as _
-from django.views.generic.list_detail import object_list
-from ajaxable.utils import LazyEncoder, JSONResponse, AjaxableFormView
+from ajaxable.utils import JSONResponse, AjaxableFormView
from catalogue import models
from catalogue import forms
from catalogue.utils import (split_tags, AttachmentHttpResponse,
async_build_pdf, MultiQuerySet)
-from catalogue.tasks import touch_tag
from pdcounter import models as pdcounter_models
from pdcounter import views as pdcounter_views
from suggest.forms import PublishingSuggestForm
'main_link': book.get_absolute_url(),
'request': context.get('request'),
'hits': hits,
+ 'main_link': book.get_absolute_url(),
}
from social.models import Cite
-admin.site.register(Cite)
+class CiteAdmin(admin.ModelAdmin):
+ list_display = ['text', 'vip', 'small']
+
+
+admin.site.register(Cite, CiteAdmin)
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding field 'Cite.small'
+ db.add_column('social_cite', 'small', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'Cite.small'
+ db.delete_column('social_cite', 'small')
+
+
+ models = {
+ 'catalogue.book': {
+ 'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'},
+ '_related_info': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}),
+ 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+ 'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+ 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+ 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+ 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+ 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
+ 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+ 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+ 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+ },
+ 'social.cite': {
+ 'Meta': {'object_name': 'Cite'},
+ 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'link': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+ 'small': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'text': ('django.db.models.fields.TextField', [], {}),
+ 'vip': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['social']
#
from django.db import models
from django.utils.translation import ugettext_lazy as _
+from django.core.urlresolvers import reverse
from catalogue.models import Book
class Cite(models.Model):
book = models.ForeignKey(Book)
text = models.TextField(_('text'))
+ small = models.BooleanField(_('small'), default=False, help_text=_('Make this cite display smaller.'))
vip = models.CharField(_('VIP'), max_length=128, null=True, blank=True)
link = models.URLField(_('link'))
+ class Meta:
+ ordering = ('vip', 'text')
+
+ def __unicode__(self):
+ return u"%s: %s…" % (self.vip, self.text[:60])
+
def get_absolute_url(self):
- return self.link
+ """This is used for testing."""
+ return "%s?choose_cite=%d" % (reverse('main_page'), self.id)
{% load i18n %}
{% if cite %}
-<a href="{{ cite.get_absolute_url }}" class="cite">
+<a href="{{ cite.link }}" class="cite{% if cite.small %} cite-small{% endif %}">
{% if cite.vip %}
- <p class='vip mono'>{{ cite.vip }} {% trans "recommends" %}:</p>
+ <p class='vip mono'><span>{{ cite.vip }} {% trans "recommends" %}:</span></p>
{% endif %}
<blockquote class="cite-body">
- {{ cite.text|linebreaks|safe }}
+ <span>{{ cite.text|linebreaksbr|safe }}</span>
</blockquote>
- <p class="source mono">{{ cite.book.pretty_title }}</p>
+ <p class="source mono"><span>{{ cite.book.pretty_title }}</span></p>
</a>
{% else %}
{% if fallback %}
register.filter('likes', likes)
-@register.inclusion_tag('social/cite_promo.html')
-def cite_promo(ctx=None, fallback=False):
+@register.inclusion_tag('social/cite_promo.html', takes_context=True)
+def cite_promo(context, ctx=None, fallback=False):
"""Choose"""
- if ctx is None:
- cites = Cite.objects.all()
- elif isinstance(ctx, Book):
- cites = ctx.cite_set.all()
- if not cites.exists():
- cites = cites_for_tags([ctx.book_tag()])
- else:
- cites = cites_for_tags(ctx)
+ try:
+ request = context['request']
+ assert request.user.is_staff
+ assert 'choose_cite' in request.GET
+ cite = Cite.objects.get(pk=request.GET['choose_cite'])
+ except AssertionError, Cite.DoesNotExist:
+ if ctx is None:
+ cites = Cite.objects.all()
+ elif isinstance(ctx, Book):
+ cites = ctx.cite_set.all()
+ if not cites.exists():
+ cites = cites_for_tags([ctx.book_tag()])
+ else:
+ cites = cites_for_tags(ctx)
+ cite = cites.order_by('?')[0] if cites.exists() else None
return {
- 'cite': cites.order_by('?')[0] if cites.exists() else None,
+ 'cite': cite,
'fallback': fallback,
'ctx': ctx,
}
-Subproject commit e394602de9243608d1e99a3de448a75646f1a77f
+Subproject commit b24b166cc4de6ba7e9b1559717bb5ff6e27bdacd
+++ /dev/null
-# Code taken from ActiveState Python recipes:
-# http://code.activestate.com/recipes/389023/
-#
-# Changed by Marek Stepniowski <marek@stepniowski.com> to handle unicode characters
-import xml.sax
-
-
-class simpleHandler(xml.sax.ContentHandler):
- """A simple handler that provides us with indices of marked up content."""
- def __init__(self):
- self.elements = [] #this will contain a list of elements and their start/end indices
- self.open_elements = [] #this holds info on open elements while we wait for their close
- self.content = ""
-
- def startElement(self, name, attrs):
- if name == 'foobar': return # we require an outer wrapper, which we promptly ignore.
- self.open_elements.append({'name':name,
- 'attrs':attrs.copy(),
- 'start':len(self.content),
- })
-
- def endElement(self, name):
- if name == 'foobar': return # we require an outer wrapper, which we promptly ignore.
- for i in range(len(self.open_elements)):
- e = self.open_elements[i]
- if e['name'] == name:
- # append a (start,end), name, attrs
- self.elements.append(((e['start'], #start position
- len(self.content)), # current (end) position
- e['name'], e['attrs'])
- )
- del self.open_elements[i]
- return
-
- def characters(self, chunk):
- self.content += chunk
-
-
-class MarkupString(unicode):
- """A simple class for dealing with marked up strings. When we are sliced, we return
- valid marked up strings, preserving markup."""
- def __init__(self, string):
- unicode.__init__(self)
- self.handler = simpleHandler()
- xml.sax.parseString((u"<foobar>%s</foobar>" % string).encode('utf-8'), self.handler)
- self.raw = self.handler.content
-
- def __getitem__(self, n):
- return self.__getslice__(n, n + 1)
-
- def __getslice__(self, s, e):
- # only include relevant elements
- if not e or e > len(self.raw): e = len(self.raw)
- elements = filter(lambda tp: (tp[0][1] >= s and # end after the start...
- tp[0][0] <= e # and start before the end
- ),
- self.handler.elements)
- ends = {}
- starts = {}
- for el in elements:
- # cycle through elements that effect our slice and keep track of
- # where their start and end tags should go.
- pos = el[0]
- name = el[1]
- attrs = el[2]
- # write our start tag <stag att="val"...>
- stag = "<%s" % name
- for k, v in attrs.items(): stag += " %s=%s" % (k, xml.sax.saxutils.quoteattr(v))
- stag += ">"
- etag = "</%s>" % name # simple end tag
- spos = pos[0]
- epos = pos[1]
- if spos < s: spos = s
- if epos > e: epos = e
- if epos != spos: # we don't care about tags that don't markup any text
- if not starts.has_key(spos): starts[spos] = []
- starts[spos].append(stag)
- if not ends.has_key(epos): ends[epos] = []
- ends[epos].append(etag)
- outbuf = "" # our actual output string
- for pos in range(s, e): # we move through positions
- char = self.raw[pos]
- if ends.has_key(pos): # if there are endtags to insert...
- for et in ends[pos]: outbuf += et
- if starts.has_key(pos): # if there are start tags to insert
- mystarts = starts[pos]
- # reverse these so the order works out,e.g. <i><b><u></u></b></i>
- mystarts.reverse()
- for st in mystarts: outbuf += st
- outbuf += char
- if ends.has_key(e):
- for et in ends[e]: outbuf += et
- return MarkupString(outbuf)
-
- def __len__(self):
- return len(self.raw)
-
#footer {
- color: #777;
- eborder-top: 1px solid #ddd;
+ color: #767676;
margin-top: 5em;
padding-top:3em;
background: #fff;
background: white;
padding: 3em 2em .1em 8em;
}
-.cite-body {
+.book-wide-box .cite-body,
+#tagged-object-list .cite-body
+ {
font-size: 1.8em;
line-height: 1.3em;
}
-.cite p {
+.book-wide-box .source,
+#tagged-object-list .source
+{
color: #444;
font-size: 1.1em;
margin-top: 1.6em;
}
-
.cite .vip {
margin: 0;
color: #575C63;
}
+/* a long cite displays smaller */
+.cite-small .cite-body span {
+ font-size: 1.4em;
+}
+
#big-cite {
- background-color: white;
+ background-color: #5f3e1c; /* average image color */
+ color: white;
padding: 0;
margin: 0;
+ background-image: url(/static/img/backdrop/boltron-3212284622.jpg);
+ background-size: 100%;
+ background-position: 50% 50%;
}
#big-cite .cite {
- padding: 10.75em 10em 8.5em 18.2em;
+ padding: 4.6em 4em 4.8em 0;
+ background: none;
+ color: white;
}
#big-cite h2 {
#big-cite .cite-body {
- margin: .05em;
- font-size: 2.8em;
- line-height: 1.2em;
- color: #191919;
+ margin: .05em .05em .05em 17.5em;
+}
+#big-cite .cite-body span {
+ font-size: 3em;
+ line-height: 1.16em;
+}
+
+#big-cite .vip {
+ float:left;
+ text-align:right;
+ width: 14.7em;
+ margin-top: .25em;
+}
+
+#big-cite .vip span {
+ font-size:1.1em;
+}
+
+#big-cite .cite-body span,
+#big-cite .vip span,
+#big-cite .source span
+{
+ color: white;
+ background-color: rgb(0, 0, 0);
+ background-color: rgba(0, 0, 0, 0.6);
+ /* For IE 5.5 - 7*/
+ filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);
+ /* For IE 8*/
+ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";
+}
+/* a long cite displays smaller */
+#big-cite .cite-small .cite-body span {
+ font-size: 2em;
}
#big-cite .source {
- color: #00a1ac;
- margin: 0;
+ margin: 1.6em 0.2em 1.6em 17.5em;
+}
+#big-cite .source span {
font-size: 1.1em;
- margin: 1.1em 0.2em;
}
+
.cite blockquote p {
margin: 0;
}
top: -0.8em;
right: -1em;
background-color: #f7f7f7;
- vertical-align: center;
+ vertical-align: middle;
width: 39.5em;
margin: 0;
padding: 1em;
#half-header-content {
background: #191919;
+ color: #989898;
}
#search-area {
margin: 0;
background: #444;
+ color: white;
margin-left: 24em;
width: 73.5em;
}
#search-button {
display: inline-block;
background: #02adb7;
+ color: white;
padding: 0;
margin: 0;
width: 9.4em;
{% for name, slug in related.tags.author %}
<a href="{% tag_url 'author' slug %}">{{ name }}</a>{% if not forloop.last %},
{% endif %}{% endfor %}{% for title, slug in related.parents %},
- <a href="{% url book_detail slug %}">{{ title }}</a>
- {% endfor %}
+ <a href="{% url book_detail slug %}">{{ title }}</a>{% endfor %}
</div>
<div class="title"><a href="{{ main_link }}">{{ book.title }}</a></div>
</div>
</div>
</div>
- {% block book-box-extra-info %}{% endblock %}
-
<ul class="book-box-tools">
<li class="book-box-read">
{% if book.html_file %}
{% endif %}
</li>
</ul>
+ {% block book-box-extra-info %}{% endblock %}
{% block box-append %}
{% endblock %}
<div class="clearboth"></div>
-{% load removewholetags from catalogue_tags %}
-
{% if fragment %}
<a href="{{ fragment.get_absolute_url }}" class="cite">
<blockquote class="cite-body">
- {{ fragment.text|removewholetags:"a"|truncatewords_html:15|safe }}
+ {{ fragment.get_short_text|safe }}
</blockquote>
- <p class="mono">{{ fragment.book.pretty_title }}</p>
+ <p class="mono source">{{ fragment.book.pretty_title }}</p>
</a>
{% endif %}
{% extends "base.html" %}
-{% load cache i18n catalogue_tags infopages_tags social_tags %}
+{% load cache chunks i18n catalogue_tags infopages_tags social_tags %}
{% block title %}{% trans "Wolne Lektury internet library" %}{% endblock %}
<div id="promo-box">
- <div class="grid-line" id="promo-box-header">
- <h2 class="mono">Trwa konkurs</h2>
- </div>
- <div id="promo-box-body" class="accent4">
- <p id="promo-box-title" class="mono"><span>Konkurs poezji automatycznej</span></p>
- <div id="promo-box-content">
- <p>Znacie Leśmianatora? To niewielki skrypt miskujący na życzenie
- wiersze z Wolnych Lektur.</p>
- </div>
- </div>
+ {% chunk "promo" %}
</div>
<h2 class="main-last"><span class="mono">Ostatnie publikacje</span></h2>
- {% cache 300 last-published-on-main %}
+ {% cache 60 last-published-on-main %}
{% for book in last_published %}
{% book_mini book %}
{% endfor %}
{% endspaceless %}
{% endblock %}
+
+
+{% block add_footer %}
+<p>{% trans "Image used:" %}
+<a href="http://www.flickr.com/photos/boltron/3212284622/">Everyone loves books…</a>,
+boltron-@Flickr,
+<a href="http://creativecommons.org/licenses/by-sa/2.0/deed.pl">CC BY-SA</a>.
+</p>
+{% endblock %}