self.fields['set_ids'] = forms.MultipleChoiceField(
label=_('Shelves'),
required=False,
- choices=[(tag.id, "%s (%s)" % (tag.name, tag.book_count)) for tag in Tag.objects.filter(category='set', user=user)],
+ choices=[(tag.id, "%s (%s)" % (tag.name, tag.get_count())) for tag in Tag.objects.filter(category='set', user=user)],
initial=[tag.id for tag in obj.tags.filter(category='set', user=user)],
widget=forms.CheckboxSelectMultiple
)
--- /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):
+
+ # Changing field 'Tag.book_count'
+ db.alter_column('catalogue_tag', 'book_count', self.gf('django.db.models.fields.IntegerField')(null=True))
+
+ if not db.dry_run:
+ from django.contrib.contenttypes.models import ContentType
+ from simplejson import loads, dumps
+
+ manager = orm.TagRelation.objects
+
+ def type_id(model):
+ return ContentType.objects.get_for_model(model).pk
+
+ def tagged_with_any(model, tags):
+ object_ids = {}
+ for relation in manager.filter(content_type=type_id(model), tag__in=tags):
+ object_ids[relation.object_id] = 1
+ return model.objects.filter(pk__in=object_ids.keys())
+
+ def get_tags(instance):
+ return [relation.tag for relation in manager.filter(
+ content_type=type_id(type(instance)), object_id=instance.pk).select_related()]
+
+ def refresh_book_count(tag):
+ if tag.category == 'theme':
+ objects = tagged_with_any(orm.Fragment, [tag]).only()
+ else:
+ objects = tagged_with_any(orm.Book, [tag]).only('slug')
+ if tag.category != 'set':
+ # eliminate descendants
+ l_tags = orm.Tag.objects.filter(slug__in=['l-'+book.slug for book in objects])
+ descendants_keys = [book.pk for book in tagged_with_any(orm.Book, l_tags)]
+ if descendants_keys:
+ objects = objects.exclude(pk__in=descendants_keys)
+ tag.book_count = objects.count()
+ tag.save()
+
+ def refresh_tag_counter(book):
+ tags = {}
+ for child in book.children.all().order_by():
+ for tag_pk, value in tag_counter(child).iteritems():
+ tags[tag_pk] = tags.get(tag_pk, 0) + value
+ for tag in [tag for tag in get_tags(book) if tag.category not in ('book', 'theme', 'set')]:
+ tags[tag.pk] = 1
+ book._tag_counter = dumps(tags)
+ book.save()
+ return tags
+
+ def tag_counter(book):
+ if book._tag_counter is None:
+ return refresh_tag_counter(book)
+ return dict((int(k), v) for k, v in loads(book._tag_counter).iteritems())
+
+ def theme_counter(book):
+ if book._theme_counter is None:
+ tags = {}
+ l_tag = orm.Tag.objects.get(slug='l-'+book.slug)
+ for fragment in tagged_with_any(orm.Fragment, [l_tag]):
+ for tag in [tag for tag in get_tags(fragment) if tag.category=='theme']:
+ tags[tag.pk] = tags.get(tag.pk, 0) + 1
+ book._theme_counter = dumps(tags)
+ book.save()
+
+
+ # remove orphaned relations
+ book_type_id = type_id(orm.Book)
+ book_ids = [b.pk for b in orm.Book.objects.all().only()]
+ manager.filter(content_type=book_type_id).exclude(object_id__in=book_ids).delete()
+ del book_ids
+
+ fragment_type_id = type_id(orm.Fragment)
+ fragment_ids = [b.pk for b in orm.Fragment.objects.all().only()]
+ manager.filter(content_type=fragment_type_id).exclude(object_id__in=fragment_ids).delete()
+ del fragment_ids
+
+ tag_ids = [t.pk for t in orm.Tag.objects.all().only()]
+ manager.exclude(tag__in=tag_ids).delete()
+ del tag_ids
+
+ # remove theme tags for books
+ manager.filter(content_type=book_type_id).filter(tag__category='theme').delete()
+
+ # reset count fields
+ for tag in orm.Tag.objects.exclude(category__in=('book', 'set')).iterator():
+ refresh_book_count(tag)
+ for book in orm.Book.objects.all().iterator():
+ theme_counter(book)
+ for book in orm.Book.objects.filter(parent=None).iterator():
+ tag_counter(book)
+
+ def backwards(self, orm):
+
+ # Changing field 'Tag.book_count'
+ db.alter_column('catalogue_tag', 'book_count', self.gf('django.db.models.fields.IntegerField')())
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'catalogue.book': {
+ 'Meta': {'object_name': 'Book'},
+ '_short_html': ('django.db.models.fields.TextField', [], {}),
+ '_short_html_de': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_en': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_es': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_fr': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_lt': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_pl': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_ru': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_uk': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_tag_counter': ('catalogue.fields.JSONField', [], {'null': 'True'}),
+ '_theme_counter': ('catalogue.fields.JSONField', [], {'null': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': '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', [], {}),
+ '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'}),
+ 'mp3_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'odt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'ogg_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'}),
+ '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'})
+ },
+ 'catalogue.bookstub': {
+ 'Meta': {'object_name': 'BookStub'},
+ 'author': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'pd': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+ 'translator': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'translator_death': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+ },
+ 'catalogue.fragment': {
+ 'Meta': {'object_name': 'Fragment'},
+ '_short_html': ('django.db.models.fields.TextField', [], {}),
+ '_short_html_de': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_en': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_es': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_fr': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_lt': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_pl': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_ru': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ '_short_html_uk': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}),
+ 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+ 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'short_text': ('django.db.models.fields.TextField', [], {}),
+ 'text': ('django.db.models.fields.TextField', [], {})
+ },
+ 'catalogue.tag': {
+ 'Meta': {'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+ 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+ 'death': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'main_page': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+ 'sort_key': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+ },
+ 'catalogue.tagrelation': {
+ 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['catalogue']
from django.core.urlresolvers import reverse
from datetime import datetime
-from newtagging.models import TagBase
+from newtagging.models import TagBase, tags_updated
from newtagging import managers
from catalogue.fields import JSONField
main_page = models.BooleanField(_('main page'), default=False, db_index=True, help_text=_('Show tag on main page'))
user = models.ForeignKey(User, blank=True, null=True)
- book_count = models.IntegerField(_('book count'), default=0, blank=False, null=False)
+ book_count = models.IntegerField(_('book count'), blank=False, null=True)
death = models.IntegerField(_(u'year of death'), blank=True, null=True)
gazeta_link = models.CharField(blank=True, max_length=240)
wiki_link = models.CharField(blank=True, max_length=240)
""" calculates the year of public domain entry for an author """
return self.death + 71 if self.death is not None else None
+ def get_count(self):
+ """ returns global book count for book tags, fragment count for themes """
+
+ if self.book_count is None:
+ if self.category == 'book':
+ # never used
+ objects = Book.objects.none()
+ elif self.category == 'theme':
+ objects = Fragment.tagged.with_all((self,))
+ else:
+ objects = Book.tagged.with_all((self,)).order_by()
+ if self.category != 'set':
+ # eliminate descendants
+ l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects])
+ descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
+ if descendants_keys:
+ objects = objects.exclude(pk__in=descendants_keys)
+ self.book_count = objects.count()
+ self.save()
+ return self.book_count
+
@staticmethod
def get_tag_list(tags):
if isinstance(tags, basestring):
def name(self):
return self.title
+ def book_tag_slug(self):
+ return ('l-' + self.slug)[:120]
+
def book_tag(self):
- slug = ('l-' + self.slug)[:120]
+ slug = self.book_tag_slug()
book_tag, created = Tag.objects.get_or_create(slug=slug, category='book')
if created:
book_tag.name = self.title[:50]
tag.save()
book_tags.append(tag)
- book.tags = book_tags
+ book.tags = book_tags + book_shelves
book_tag = book.book_tag()
# Extract fragments
closed_fragments, open_fragments = html.extract_fragments(book.html_file.path)
- book_themes = []
for fragment in closed_fragments.values():
text = fragment.to_string()
short_text = ''
tag.save()
themes.append(tag)
new_fragment.save()
- new_fragment.tags = set(list(book.tags) + themes + [book_tag])
- book_themes += themes
+ new_fragment.tags = set(book_tags + themes + [book_tag])
- book_themes = set(book_themes)
- book.tags = list(book.tags) + list(book_themes) + book_shelves
+ # refresh cache
+ book.tag_counter
+ book.theme_counter
book.save()
return book
self.save(reset_short_html=False, refresh_mp3=False)
return tags
+ def reset_tag_counter(self):
+ self._tag_counter = None
+ self.save(reset_short_html=False, refresh_mp3=False)
+ if self.parent:
+ self.parent.reset_tag_counter()
+
@property
def tag_counter(self):
if self._tag_counter is None:
self.save(reset_short_html=False, refresh_mp3=False)
return tags
+ def reset_theme_counter(self):
+ self._theme_counter = None
+ self.save(reset_short_html=False, refresh_mp3=False)
+ if self.parent:
+ self.parent.reset_theme_counter()
+
@property
def theme_counter(self):
if self._theme_counter is None:
return self.title
+def _tags_updated_handler(sender, affected_tags, **kwargs):
+ # reset tag global counter
+ Tag.objects.filter(pk__in=[tag.pk for tag in affected_tags]).update(book_count=None)
+
+ # if book tags changed, reset book tag counter
+ if isinstance(sender, Book) and \
+ Tag.objects.filter(pk__in=(tag.pk for tag in affected_tags)).\
+ exclude(category__in=('book', 'theme', 'set')).count():
+ sender.reset_tag_counter()
+ # if fragment theme changed, reset book theme counter
+ elif isinstance(sender, Fragment) and \
+ Tag.objects.filter(pk__in=(tag.pk for tag in affected_tags)).\
+ filter(category='theme').count():
+ sender.book.reset_theme_counter()
+tags_updated.connect(_tags_updated_handler)
+
@register.inclusion_tag('catalogue/folded_tag_list.html')
def folded_tag_list(tags, choices=None):
+ tags = [tag for tag in tags if tag.count]
if choices is None:
choices = []
some_tags_hidden = False
self.assertEqual(book.fragments.count(), 1)
self.assertEqual(book.fragments.all()[0].text, u'<p class="paragraph">Ala ma kota</p>\n')
- self.assert_(('theme', 'love') in [ (tag.category, tag.slug) for tag in book.tags ])
+ self.assert_(('theme', 'love') in [ (tag.category, tag.slug) for tag in book.fragments.all()[0].tags ])
def test_book_replace_title(self):
BOOK_TEXT = """<utwor />"""
models.Book.objects.all().delete()
cats = self.client.get('/katalog/k/').context['categories']
self.assertEqual(cats, {})
+ self.assertEqual(models.Fragment.objects.all().count(), 0,
+ "orphaned fragments left")
+ self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0,
+ "orphaned TagRelation objects left")
def test_deleted_tag(self):
""" there should be no tag relations left after deleting tags """
models.Tag.objects.all().delete()
cats = self.client.get('/katalog/lektura/book/').context['categories']
self.assertEqual(cats, {})
+ self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0,
+ "orphaned TagRelation objects left")
class TestIdenticalTag(WLTestCase):
""" there should be all related tags in relevant categories """
models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info)
- cats = self.client.get('/katalog/lektura/tag/').context['categories']
- for category in 'author', 'kind', 'genre', 'epoch', 'theme':
- self.assertTrue('tag' in [tag.slug for tag in cats[category]],
+ context = self.client.get('/katalog/lektura/tag/').context
+ for category in 'author', 'kind', 'genre', 'epoch':
+ self.assertTrue('tag' in [tag.slug for tag in context['categories'][category]],
'missing related tag for %s' % category)
+ self.assertTrue('tag' in [tag.slug for tag in context['book_themes']])
def test_qualified_url(self):
models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info)
self.assertEqual(1, len(context['object_list']))
self.assertNotEqual({}, context['categories'])
self.assertFalse(cat in context['categories'])
+
+
+class BookTagsTests(WLTestCase):
+ """ tests the /katalog/lektura/book/ page for related tags """
+
+ def setUp(self):
+ WLTestCase.setUp(self)
+ author1 = PersonStub(("Common",), "Man")
+ author2 = PersonStub(("Jim",), "Lazy")
+
+ child_info = BookInfoStub(authors=(author1, author2), genre="ChildGenre", epoch='Epoch', kind="ChildKind",
+ **info_args(u"Child"))
+ parent_info = BookInfoStub(author=author1, genre="Genre", epoch='Epoch', kind="Kind",
+ parts=[child_info.url],
+ **info_args(u"Parent"))
+
+ for info in child_info, parent_info:
+ book_text = """<utwor><opowiadanie><akap>
+ <begin id="m01" />
+ <motyw id="m01">Theme, %sTheme</motyw>
+ Ala ma kota
+ <end id="m01" />
+ </akap></opowiadanie></utwor>
+ """ % info.title.encode('utf-8')
+ book = models.Book.from_text_and_meta(ContentFile(book_text), info)
+
+ def test_book_tags(self):
+ """ book should have own tags and whole tree's themes """
+
+ context = self.client.get('/katalog/lektura/parent/').context
+
+ self.assertEqual([tag.name for tag in context['categories']['author']],
+ ['Common Man'])
+ self.assertEqual([tag.name for tag in context['categories']['kind']],
+ ['Kind'])
+ self.assertEqual([(tag.name, tag.count) for tag in context['book_themes']],
+ [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)])
+
+ def test_main_page_tags(self):
+ """ test main page tags and counts """
+
+ context = self.client.get('/katalog/').context
+
+ self.assertEqual([(tag.name, tag.count) for tag in context['categories']['author']],
+ [('Jim Lazy', 1), ('Common Man', 1)])
+ self.assertEqual([(tag.name, tag.count) for tag in context['fragment_tags']],
+ [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)])
+
if request.user.is_authenticated():
shelves = models.Tag.objects.filter(category='set', user=request.user)
new_set_form = forms.NewSetForm()
- extra_where = "NOT catalogue_tag.category = 'set'"
- tags = models.Tag.objects.usage_for_model(models.Book, counts=True, extra={'where': [extra_where]})
- fragment_tags = models.Tag.objects.usage_for_model(models.Fragment, counts=True,
- extra={'where': ["catalogue_tag.category = 'theme'"] + [extra_where]})
+
+ tags = models.Tag.objects.exclude(category__in=('set', 'book'))
+ for tag in tags:
+ tag.count = tag.get_count()
categories = split_tags(tags)
+ fragment_tags = categories.get('theme', [])
form = forms.SearchForm()
return render_to_response('catalogue/main_page.html', locals(),
if shelf_tags:
books = models.Book.tagged.with_all(shelf_tags).order_by()
- l_tags = [book.book_tag() for book in books]
+ l_tags = models.Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books])
fragments = models.Fragment.tagged.with_any(l_tags, fragments)
# newtagging goes crazy if we just try:
objects = models.Book.tagged.with_all(tags).order_by()
if not shelf_is_set:
# eliminate descendants
- l_tags = [book.book_tag() for book in objects]
+ l_tags = models.Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() 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)
tags = list(book.tags.filter(~Q(category='set')))
categories = split_tags(tags)
book_children = book.children.all().order_by('parent_number')
- extra_where = "catalogue_tag.category = 'theme'"
- book_themes = models.Tag.objects.related_for_model(book_tag, models.Fragment, counts=True, extra={'where': [extra_where]})
+
+ theme_counter = book.theme_counter
+ book_themes = models.Tag.objects.filter(pk__in=theme_counter.keys())
+ for tag in book_themes:
+ tag.count = theme_counter[tag.pk]
+
extra_info = book.get_extra_info_value()
form = forms.SearchForm()
new_shelves = [models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']]
for shelf in [shelf for shelf in old_shelves if shelf not in new_shelves]:
- shelf.book_count -= 1
+ shelf.book_count = None
shelf.save()
for shelf in [shelf for shelf in new_shelves if shelf not in old_shelves]:
- shelf.book_count += 1
+ shelf.book_count = None
shelf.save()
book.tags = new_shelves + list(book.tags.filter(~Q(category='set') | ~Q(user=request.user)))
if shelf in book.tags:
models.Tag.objects.remove_tag(book, shelf)
- shelf.book_count -= 1
+ shelf.book_count = None
shelf.save()
return HttpResponse(_('Book was successfully removed from the shelf'))
from django.utils.translation import ugettext_lazy as _
from django.db.models.base import ModelBase
from django.core.exceptions import ObjectDoesNotExist
+from django.dispatch import Signal
qn = connection.ops.quote_name
parse_lookup = None
+tags_updated = Signal(providing_args=["affected_tags"])
+
def get_queryset_and_model(queryset_or_model):
"""
Given a ``QuerySet`` or a ``Model``, returns a two-tuple of
def __init__(self, intermediary_table_model):
super(TagManager, self).__init__()
self.intermediary_table_model = intermediary_table_model
+ models.signals.pre_delete.connect(self.target_deleted)
+
+ def target_deleted(self, instance, **kwargs):
+ """ clear tag relations before deleting an object """
+ try:
+ int(instance.pk)
+ except ValueError:
+ return
+
+ self.update_tags(instance, [])
def update_tags(self, obj, tags):
"""
object_id=obj.pk,
tag__in=tags_for_removal).delete()
# Add new tags
- for tag in updated_tags:
+ tags_to_add = [tag for tag in updated_tags
+ if tag not in current_tags]
+ for tag in tags_to_add:
if tag not in current_tags:
self.intermediary_table_model._default_manager.create(tag=tag, content_object=obj)
+ tags_updated.send(sender=obj, affected_tags=tags_to_add + tags_for_removal)
+
def remove_tag(self, obj, tag):
"""
Remove tag from an object.
var LOCALE_TEXTS = {
- "pl": {
- "DELETE_SHELF": "Czy na pewno usunąć półkę",
- "HIDE_DESCRIPTION": "Zwiń opis",
- "EXPAND_DESCRIPTION": "Rozwiń opis",
- "LOADING": "Ładowanie"
- },
+ "pl": {
+ "DELETE_SHELF": "Czy na pewno usunąć półkę",
+ "HIDE_DESCRIPTION": "Zwiń opis",
+ "EXPAND_DESCRIPTION": "Rozwiń opis",
+ "LOADING": "Ładowanie"
+ },
"de": {
"DELETE_SHELF": "Translate me!",
"HIDE_DESCRIPTION": "Translate me!",
"EXPAND_DESCRIPTION": "Translate me!",
"LOADING": "Translate me!"
},
- "fr": {
- "DELETE_SHELF": "Translate me!",
- "HIDE_DESCRIPTION": "Translate me!",
- "EXPAND_DESCRIPTION": "Translate me!",
- "LOADING": "Translate me!"
- },
- "en": {
- "DELETE_SHELF": "Translate me!",
- "HIDE_DESCRIPTION": "Translate me!",
- "EXPAND_DESCRIPTION": "Translate me!",
- "LOADING": "Translate me!"
- },
- "ru": {
- "DELETE_SHELF": "Translate me!",
- "HIDE_DESCRIPTION": "Translate me!",
- "EXPAND_DESCRIPTION": "Translate me!",
- "LOADING": "Translate me!"
- },
- "es": {
- "DELETE_SHELF": "Translate me!",
- "HIDE_DESCRIPTION": "Translate me!",
- "EXPAND_DESCRIPTION": "Translate me!",
- "LOADING": "Translate me!"
- },
+ "fr": {
+ "DELETE_SHELF": "Translate me!",
+ "HIDE_DESCRIPTION": "Translate me!",
+ "EXPAND_DESCRIPTION": "Translate me!",
+ "LOADING": "Translate me!"
+ },
+ "en": {
+ "DELETE_SHELF": "Translate me!",
+ "HIDE_DESCRIPTION": "Translate me!",
+ "EXPAND_DESCRIPTION": "Translate me!",
+ "LOADING": "Translate me!"
+ },
+ "ru": {
+ "DELETE_SHELF": "Translate me!",
+ "HIDE_DESCRIPTION": "Translate me!",
+ "EXPAND_DESCRIPTION": "Translate me!",
+ "LOADING": "Translate me!"
+ },
+ "es": {
+ "DELETE_SHELF": "Translate me!",
+ "HIDE_DESCRIPTION": "Translate me!",
+ "EXPAND_DESCRIPTION": "Translate me!",
+ "LOADING": "Translate me!"
+ },
"lt":{
"DELETE_SHELF": "Translate me!",
"HIDE_DESCRIPTION": "Translate me!",
short_text = p['short_text'],
long_text = p['long_text'];
- var toggle = function() {
- if (cont.hasClass('short')) {
- cont.animate({"height": long_el.attr("cont_h")+'px'}, {duration: "fast" }).removeClass('short');
- short_el.hide();
- long_el.show();
- if (button && long_text) button.html(long_text);
- } else {
- cont.animate({"height": short_el.attr("cont_h")+'px'}, {duration: "fast" }).addClass('short');
- long_el.hide();
- short_el.show();
- if (button && short_text) button.html(short_text);
+ var toggle_fun = function(cont, short_el, long_el, button, short_text, long_text) {
+ var toggle = function() {
+ if (cont.hasClass('short')) {
+ cont.animate({"height": long_el.attr("cont_h")+'px'}, {duration: "fast" }).removeClass('short');
+ short_el.hide();
+ long_el.show();
+ if (button && long_text) button.html(long_text);
+ } else {
+ cont.animate({"height": short_el.attr("cont_h")+'px'}, {duration: "fast" }).addClass('short');
+ long_el.hide();
+ short_el.show();
+ if (button && short_text) button.html(short_text);
+ }
+ return false;
}
- return false;
+ return toggle;
}
if (long_el.html().length <= short_el.html().length)
return;
if (button) button.hover(
function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); },
function() { $(this).css({background: '#EEE'}); }
- ).click(toggle);
+ ).click(toggle_fun(cont, short_el, long_el, button, short_text, long_text));
short_el.hover(
function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); },
function() { $(this).css({background: '#FFF'}); }
- ).click(toggle);
+ ).click(toggle_fun(cont, short_el, long_el, button, short_text, long_text));
long_el.hover(
function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); },
function() { $(this).css({background: '#FFF'}); }
- ).click(toggle);
+ ).click(toggle_fun(cont, short_el, long_el, button, short_text, long_text));
};
$('form input').labelify({labelledClass: 'blur'});
},
onLoad: function(hash) {
$('form', hash.w).ajaxForm({
- dataType: 'json',
+ dataType: 'json',
target: $('#suggest-window div.target'),
success: function(response) {
- if (response.success) {
- $('#suggest-window div.target').text(response.message);
+ if (response.success) {
+ $('#suggest-window div.target').text(response.message);
setTimeout(function() { $('#suggest-window').jqmHide() }, 1000)
- }
- else {
- $('#suggest-form .error').remove();
- $.each(response.errors, function(id, errors) {
- $('#suggest-form #id_' + id).before('<span class="error">' + errors[0] + '</span>');
- });
- $('#suggest-form input[type=submit]').removeAttr('disabled');
- return false;
- }
- }
+ }
+ else {
+ $('#suggest-form .error').remove();
+ $.each(response.errors, function(id, errors) {
+ $('#suggest-form #id_' + id).before('<span class="error">' + errors[0] + '</span>');
+ });
+ $('#suggest-form input[type=submit]').removeAttr('disabled');
+ return false;
+ }
+ }
});
}
});
target.html('<p><img src="/static/img/indicator.gif" />'+LOCALE_TEXTS[LANGUAGE_CODE]['DELETE_SHELF']+'</p>');
hash.w.css({position: 'absolute', left: offset.left, top: offset.top}).show() },
onLoad: function(hash) {
- try {
- $('#createShelfTrigger').click(function(){
- $('#createNewShelf').show();
- });
- } catch (e){}
+ try {
+ $('#createShelfTrigger').click(function(){
+ $('#createNewShelf').show();
+ });
+ } catch (e){}
$('form', hash.w).ajaxForm({
target: target,
success: function() {
- setTimeout(function() {
- $('#set-window').jqmHide();
- }, 1000)}
+ setTimeout(function() {
+ $('#set-window').jqmHide();
+ }, 1000)}
});
}
});
});
$('#share-shelf').hide().addClass('hidden');
- $('#share-shelf input').focus(function(){this.select();});
+ $('#share-shelf input').focus(function(){this.select();});
$('#user-info').show();
changeBannerText();
{% if shelves %}
<ul class="shelf-list">
{% for shelf in shelves %}
- <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "delete" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.book_count }})</a></li>
+ <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "delete" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.get_count }})</a></li>
{% endfor %}
</ul>
{% else %}
{% if shelves %}
<ul class="shelf-list">
{% for shelf in shelves %}
- <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "remove" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.book_count }})</a></li>
+ <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "remove" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.get_count }})</a></li>
{% endfor %}
</ul>
{% else %}