1 # This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later.
 
   2 # Copyright © Fundacja Wolne Lektury. 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.contrib.auth.models import User
 
  14 from django.contrib.contenttypes.models import ContentType
 
  15 from django.contrib.contenttypes.fields import GenericForeignKey
 
  16 from django.conf import settings
 
  17 from django.urls import reverse
 
  19 from catalogue.models import Book, Tag
 
  22 class Poem(models.Model):
 
  23     slug = models.SlugField('slug', max_length=120, db_index=True)
 
  24     text = models.TextField('tekst')
 
  25     created_by = models.ForeignKey(User, models.SET_NULL, null=True)
 
  26     created_from = models.TextField('dodatkowe informacje', null=True, blank=True)
 
  27     created_at = models.DateTimeField('data utworzenia', auto_now_add=True, editable=False)
 
  28     seen_at = models.DateTimeField('data ostatniego obejrzenia', auto_now_add=True, editable=False)
 
  29     view_count = models.IntegerField('licznik obejrzeń', default=1)
 
  32         f = open(settings.LESMIANATOR_PICKLE, 'rb')
 
  33         global_dictionary = pickle.load(f)
 
  35     except (IOError, AttributeError, PickleError):
 
  36         global_dictionary = {}
 
  40         self.seen_at = datetime.utcnow().replace(tzinfo=utc)
 
  44         return "%s (%s...)" % (self.slug, self.text[:20])
 
  47     def choose_letter(word, continuations):
 
  48         if word not in continuations:
 
  51         choices = sum((continuations[word][letter]
 
  52                        for letter in continuations[word]))
 
  53         r = randint(0, choices - 1)
 
  55         for letter in continuations[word]:
 
  56             r -= continuations[word][letter]
 
  61     def write(cls, continuations=None, length=3, min_lines=2, maxlen=1000):
 
  62         if continuations is None:
 
  63             continuations = cls.global_dictionary
 
  70         finished_stanza_verses = 0
 
  71         current_stanza_verses = 0
 
  76         # do `min_lines' non-empty verses and then stop,
 
  77         # but let Lesmianator finish his last stanza.
 
  78         while finished_stanza_verses < min_lines and char_count < maxlen:
 
  79             letter = cls.choose_letter(word, continuations)
 
  80             letters.append(letter)
 
  81             word = word[-length + 1:] + letter
 
  86                     finished_stanza_verses += current_stanza_verses
 
  87                     current_stanza_verses = 0
 
  89                     current_stanza_verses += 1
 
  94         return ''.join(letters).strip()
 
  96     def get_absolute_url(self):
 
  97         return reverse('get_poem', kwargs={'poem': self.slug})
 
 100 class Continuations(models.Model):
 
 101     pickle = models.FileField('plik kontynuacji', upload_to='lesmianator')
 
 102     content_type = models.ForeignKey(ContentType, models.CASCADE)
 
 103     object_id = models.PositiveIntegerField()
 
 104     content_object = GenericForeignKey('content_type', 'object_id')
 
 107         unique_together = (('content_type', 'object_id'), )
 
 110         return "Continuations for: %s" % str(self.content_object)
 
 113     def join_conts(a, b):
 
 115             a.setdefault(pre, {})
 
 117                 a[pre].setdefault(post, 0)
 
 118                 a[pre][post] += b[pre][post]
 
 122     def for_book(cls, book, length=3):
 
 123         # count from this book only
 
 124         wldoc = book.wldocument(parse_dublincore=False)
 
 125         output = wldoc.as_text(('raw-text',)).get_bytes()
 
 130         for letter in output.decode('utf-8').strip().lower():
 
 131             mydict = conts.setdefault(last_word, {})
 
 132             mydict.setdefault(letter, 0)
 
 134             last_word = last_word[-length+1:] + letter
 
 136         return reduce(cls.join_conts,
 
 137                       (cls.get(child) for child in book.children.all().iterator()),
 
 141     def for_set(cls, tag):
 
 142         books = Book.tagged_top_level([tag])
 
 143         cont_tabs = (cls.get(b) for b in books.iterator())
 
 144         return reduce(cls.join_conts, cont_tabs)
 
 148         object_type = ContentType.objects.get_for_model(sth)
 
 149         should_keys = {sth.id}
 
 150         if isinstance(sth, Tag):
 
 151             should_keys = set(b.pk for b in Book.tagged.with_any((sth,)).iterator())
 
 153             obj = cls.objects.get(content_type=object_type, object_id=sth.id)
 
 155                 raise cls.DoesNotExist
 
 156             f = open(obj.pickle.path, 'rb')
 
 157             keys, conts = pickle.load(f)
 
 159             if set(keys) != should_keys:
 
 160                 raise cls.DoesNotExist
 
 162         except cls.DoesNotExist:
 
 163             if isinstance(sth, Book):
 
 164                 conts = cls.for_book(sth)
 
 165             elif isinstance(sth, Tag):
 
 166                 conts = cls.for_set(sth)
 
 168                 raise NotImplementedError('Lesmianator continuations: only Book and Tag supported')
 
 170             c, created = cls.objects.get_or_create(content_type=object_type, object_id=sth.id)
 
 171             c.pickle.save(sth.slug+'.p', ContentFile(pickle.dumps((should_keys, conts))))