1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 
   2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 
   4 from functools import reduce
 
   6 from pickle import PickleError
 
   7 from datetime import datetime
 
   8 from random import randint
 
  10 from django.core.files.base import ContentFile
 
  11 from django.db import models
 
  12 from django.utils.timezone import utc
 
  13 from django.utils.translation import ugettext_lazy as _
 
  14 from django.core.urlresolvers import reverse
 
  15 from django.contrib.auth.models import User
 
  16 from django.contrib.contenttypes.models import ContentType
 
  17 from django.contrib.contenttypes.fields import GenericForeignKey
 
  18 from django.conf import settings
 
  20 from jsonfield import JSONField
 
  21 from catalogue.models import Book, Tag
 
  24 class Poem(models.Model):
 
  25     slug = models.SlugField(_('slug'), max_length=120, db_index=True)
 
  26     text = models.TextField(_('text'))
 
  27     created_by = models.ForeignKey(User, null=True)
 
  28     created_from = JSONField(_('extra information'), null=True, blank=True)
 
  29     created_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False)
 
  30     seen_at = models.DateTimeField(_('last view date'), auto_now_add=True, editable=False)
 
  31     view_count = models.IntegerField(_('view count'), default=1)
 
  34         f = open(settings.LESMIANATOR_PICKLE, 'rb')
 
  35         global_dictionary = pickle.load(f)
 
  37     except (IOError, AttributeError, PickleError):
 
  38         global_dictionary = {}
 
  42         self.seen_at = datetime.utcnow().replace(tzinfo=utc)
 
  46         return "%s (%s...)" % (self.slug, self.text[:20])
 
  49     def choose_letter(word, continuations):
 
  50         if word not in continuations:
 
  53         choices = sum((continuations[word][letter]
 
  54                        for letter in continuations[word]))
 
  55         r = randint(0, choices - 1)
 
  57         for letter in continuations[word]:
 
  58             r -= continuations[word][letter]
 
  63     def write(cls, continuations=None, length=3, min_lines=2, maxlen=1000):
 
  64         if continuations is None:
 
  65             continuations = cls.global_dictionary
 
  72         finished_stanza_verses = 0
 
  73         current_stanza_verses = 0
 
  78         # do `min_lines' non-empty verses and then stop,
 
  79         # but let Lesmianator finish his last stanza.
 
  80         while finished_stanza_verses < min_lines and char_count < maxlen:
 
  81             letter = cls.choose_letter(word, continuations)
 
  82             letters.append(letter)
 
  83             word = word[-length + 1:] + letter
 
  88                     finished_stanza_verses += current_stanza_verses
 
  89                     current_stanza_verses = 0
 
  91                     current_stanza_verses += 1
 
  96         return ''.join(letters).strip()
 
  98     def get_absolute_url(self):
 
  99         return reverse('get_poem', kwargs={'poem': self.slug})
 
 102 class Continuations(models.Model):
 
 103     pickle = models.FileField(_('Continuations file'), upload_to='lesmianator')
 
 104     content_type = models.ForeignKey(ContentType)
 
 105     object_id = models.PositiveIntegerField()
 
 106     content_object = GenericForeignKey('content_type', 'object_id')
 
 109         unique_together = (('content_type', 'object_id'), )
 
 112         return "Continuations for: %s" % str(self.content_object)
 
 115     def join_conts(a, b):
 
 117             a.setdefault(pre, {})
 
 119                 a[pre].setdefault(post, 0)
 
 120                 a[pre][post] += b[pre][post]
 
 124     def for_book(cls, book, length=3):
 
 125         # count from this book only
 
 126         wldoc = book.wldocument(parse_dublincore=False)
 
 127         output = wldoc.as_text(('raw-text',)).get_bytes()
 
 132         for letter in output.decode('utf-8').strip().lower():
 
 133             mydict = conts.setdefault(last_word, {})
 
 134             mydict.setdefault(letter, 0)
 
 136             last_word = last_word[-length+1:] + letter
 
 138         return reduce(cls.join_conts,
 
 139                       (cls.get(child) for child in book.children.all().iterator()),
 
 143     def for_set(cls, tag):
 
 144         books = Book.tagged_top_level([tag])
 
 145         cont_tabs = (cls.get(b) for b in books.iterator())
 
 146         return reduce(cls.join_conts, cont_tabs)
 
 150         object_type = ContentType.objects.get_for_model(sth)
 
 151         should_keys = {sth.id}
 
 152         if isinstance(sth, Tag):
 
 153             should_keys = set(b.pk for b in Book.tagged.with_any((sth,)).iterator())
 
 155             obj = cls.objects.get(content_type=object_type, object_id=sth.id)
 
 157                 raise cls.DoesNotExist
 
 158             f = open(obj.pickle.path, 'rb')
 
 159             keys, conts = pickle.load(f)
 
 161             if set(keys) != should_keys:
 
 162                 raise cls.DoesNotExist
 
 164         except cls.DoesNotExist:
 
 165             if isinstance(sth, Book):
 
 166                 conts = cls.for_book(sth)
 
 167             elif isinstance(sth, Tag):
 
 168                 conts = cls.for_set(sth)
 
 170                 raise NotImplementedError('Lesmianator continuations: only Book and Tag supported')
 
 172             c, created = cls.objects.get_or_create(content_type=object_type, object_id=sth.id)
 
 173             c.pickle.save(sth.slug+'.p', ContentFile(pickle.dumps((should_keys, conts))))