1 # -*- coding: utf-8 -*-
 
   2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 
   3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 
   6 from datetime import datetime
 
   7 from random import randint
 
   8 from StringIO import StringIO
 
  10 from django.core.files.base import ContentFile
 
  11 from django.db import models
 
  12 from django.db.models import permalink
 
  13 from django.utils.translation import ugettext_lazy as _
 
  14 from django.core.urlresolvers import reverse
 
  15 from django.db.models.signals import m2m_changed
 
  16 from django.contrib.auth.models import User
 
  17 from django.contrib.contenttypes.models import ContentType
 
  18 from django.contrib.contenttypes import generic
 
  19 from django.conf import settings
 
  21 from librarian import text
 
  22 from catalogue.fields import JSONField
 
  23 from catalogue.models import Book, Tag
 
  26 class Poem(models.Model):
 
  27     slug = models.SlugField(_('slug'), max_length=120, db_index=True)
 
  28     text = models.TextField(_('text'))
 
  29     created_by = models.ForeignKey(User, null=True)
 
  30     created_from = JSONField(_('extra information'), null=True, blank=True)
 
  31     created_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False)
 
  32     seen_at = models.DateTimeField(_('last view date'), auto_now_add=True, editable=False)
 
  33     view_count = models.IntegerField(_('view count'), default=1)
 
  36         f = open(settings.LESMIANATOR_PICKLE)
 
  37         global_dictionary = cPickle.load(f)
 
  40         global_dictionary = {}
 
  44         self.seen_at = datetime.now()
 
  47     def __unicode__(self):
 
  48         return "%s (%s...)" % (self.slug, self.text[:20])
 
  51     def choose_letter(word, continuations):
 
  52         if word not in continuations:
 
  55         choices = sum((continuations[word][letter]
 
  56                        for letter in continuations[word]))
 
  57         r = randint(0, choices - 1)
 
  59         for letter in continuations[word]:
 
  60             r -= continuations[word][letter]
 
  65     def write(cls, continuations=None, length=3, min_lines=2, maxlen=1000):
 
  66         if continuations is None:
 
  67             continuations = cls.global_dictionary
 
  74         finished_stanza_verses = 0
 
  75         current_stanza_verses = 0
 
  80         # do `min_lines' non-empty verses and then stop,
 
  81         # but let Lesmianator finish his last stanza.
 
  82         while finished_stanza_verses < min_lines and char_count < maxlen:
 
  83             letter = cls.choose_letter(word, continuations)
 
  84             letters.append(letter)
 
  85             word = word[-length + 1:] + letter
 
  90                     finished_stanza_verses += current_stanza_verses
 
  91                     current_stanza_verses = 0
 
  93                     current_stanza_verses += 1
 
  98         return ''.join(letters).strip()
 
 100     def get_absolute_url(self):
 
 101         return reverse('get_poem', kwargs={'poem': self.slug})
 
 104 class Continuations(models.Model):
 
 105     pickle = models.FileField(_('Continuations file'), upload_to='lesmianator')
 
 106     content_type = models.ForeignKey(ContentType)
 
 107     object_id = models.PositiveIntegerField()
 
 108     content_object = generic.GenericForeignKey('content_type', 'object_id')
 
 111         unique_together = (('content_type', 'object_id'), )
 
 113     def __unicode__(self):
 
 114         return "Continuations for: %s" % unicode(self.content_object)
 
 117     def join_conts(a, b):
 
 119             a.setdefault(pre, {})
 
 121                 a[pre].setdefault(post, 0)
 
 122                 a[pre][post] += b[pre][post]
 
 126     def for_book(cls, book, length=3):
 
 127         # count from this book only
 
 129         f = open(book.xml_file.path)
 
 130         text.transform(f, output, False, ('raw-text',))
 
 134         for letter in output.getvalue().decode('utf-8').strip().lower():
 
 135             mydict = conts.setdefault(last_word, {})
 
 136             mydict.setdefault(letter, 0)
 
 138             last_word = last_word[-length+1:] + letter
 
 140         return reduce(cls.join_conts, 
 
 141                       (cls.get(child) for child in book.children.all()),
 
 145     def for_set(cls, tag):
 
 146         # book contains its descendants, we don't want them twice
 
 147         books = Book.tagged.with_any((tag,))
 
 148         l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books])
 
 149         descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
 
 151             books = books.exclude(pk__in=descendants_keys)
 
 153         cont_tabs = (cls.get(b) for b in books)
 
 154         return reduce(cls.join_conts, cont_tabs)
 
 158         object_type = ContentType.objects.get_for_model(sth)
 
 159         should_keys = set([sth.id])
 
 160         if isinstance(sth, Tag):
 
 161             should_keys = set(b.pk for b in Book.tagged.with_any((sth,)))
 
 163             obj = cls.objects.get(content_type=object_type, object_id=sth.id)
 
 165                 raise cls.DoesNotExist
 
 166             f = open(obj.pickle.path)
 
 167             keys, conts = cPickle.load(f)
 
 169             if set(keys) != should_keys:
 
 170                 raise cls.DoesNotExist
 
 172         except cls.DoesNotExist:
 
 173             if isinstance(sth, Book):
 
 174                 conts = cls.for_book(sth)
 
 175             elif isinstance(sth, Tag):
 
 176                 conts = cls.for_set(sth)
 
 178                 raise NotImplemented('Lesmianator continuations: only Book and Tag supported')
 
 180             c, created = cls.objects.get_or_create(content_type=object_type, object_id=sth.id)
 
 181             c.pickle.save(sth.slug+'.p', ContentFile(cPickle.dumps((should_keys, conts))))