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 gettext_lazy as _
14 from django.contrib.auth.models import User
15 from django.contrib.contenttypes.models import ContentType
16 from django.contrib.contenttypes.fields import GenericForeignKey
17 from django.conf import settings
18 from django.urls import reverse
20 from catalogue.models import Book, Tag
23 class Poem(models.Model):
24 slug = models.SlugField(_('slug'), max_length=120, db_index=True)
25 text = models.TextField(_('text'))
26 created_by = models.ForeignKey(User, models.SET_NULL, null=True)
27 created_from = models.TextField(_('extra information'), null=True, blank=True)
28 created_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False)
29 seen_at = models.DateTimeField(_('last view date'), auto_now_add=True, editable=False)
30 view_count = models.IntegerField(_('view count'), default=1)
33 f = open(settings.LESMIANATOR_PICKLE, 'rb')
34 global_dictionary = pickle.load(f)
36 except (IOError, AttributeError, PickleError):
37 global_dictionary = {}
41 self.seen_at = datetime.utcnow().replace(tzinfo=utc)
45 return "%s (%s...)" % (self.slug, self.text[:20])
48 def choose_letter(word, continuations):
49 if word not in continuations:
52 choices = sum((continuations[word][letter]
53 for letter in continuations[word]))
54 r = randint(0, choices - 1)
56 for letter in continuations[word]:
57 r -= continuations[word][letter]
62 def write(cls, continuations=None, length=3, min_lines=2, maxlen=1000):
63 if continuations is None:
64 continuations = cls.global_dictionary
71 finished_stanza_verses = 0
72 current_stanza_verses = 0
77 # do `min_lines' non-empty verses and then stop,
78 # but let Lesmianator finish his last stanza.
79 while finished_stanza_verses < min_lines and char_count < maxlen:
80 letter = cls.choose_letter(word, continuations)
81 letters.append(letter)
82 word = word[-length + 1:] + letter
87 finished_stanza_verses += current_stanza_verses
88 current_stanza_verses = 0
90 current_stanza_verses += 1
95 return ''.join(letters).strip()
97 def get_absolute_url(self):
98 return reverse('get_poem', kwargs={'poem': self.slug})
101 class Continuations(models.Model):
102 pickle = models.FileField(_('Continuations file'), upload_to='lesmianator')
103 content_type = models.ForeignKey(ContentType, models.CASCADE)
104 object_id = models.PositiveIntegerField()
105 content_object = GenericForeignKey('content_type', 'object_id')
108 unique_together = (('content_type', 'object_id'), )
111 return "Continuations for: %s" % str(self.content_object)
114 def join_conts(a, b):
116 a.setdefault(pre, {})
118 a[pre].setdefault(post, 0)
119 a[pre][post] += b[pre][post]
123 def for_book(cls, book, length=3):
124 # count from this book only
125 wldoc = book.wldocument(parse_dublincore=False)
126 output = wldoc.as_text(('raw-text',)).get_bytes()
131 for letter in output.decode('utf-8').strip().lower():
132 mydict = conts.setdefault(last_word, {})
133 mydict.setdefault(letter, 0)
135 last_word = last_word[-length+1:] + letter
137 return reduce(cls.join_conts,
138 (cls.get(child) for child in book.children.all().iterator()),
142 def for_set(cls, tag):
143 books = Book.tagged_top_level([tag])
144 cont_tabs = (cls.get(b) for b in books.iterator())
145 return reduce(cls.join_conts, cont_tabs)
149 object_type = ContentType.objects.get_for_model(sth)
150 should_keys = {sth.id}
151 if isinstance(sth, Tag):
152 should_keys = set(b.pk for b in Book.tagged.with_any((sth,)).iterator())
154 obj = cls.objects.get(content_type=object_type, object_id=sth.id)
156 raise cls.DoesNotExist
157 f = open(obj.pickle.path, 'rb')
158 keys, conts = pickle.load(f)
160 if set(keys) != should_keys:
161 raise cls.DoesNotExist
163 except cls.DoesNotExist:
164 if isinstance(sth, Book):
165 conts = cls.for_book(sth)
166 elif isinstance(sth, Tag):
167 conts = cls.for_set(sth)
169 raise NotImplementedError('Lesmianator continuations: only Book and Tag supported')
171 c, created = cls.objects.get_or_create(content_type=object_type, object_id=sth.id)
172 c.pickle.save(sth.slug+'.p', ContentFile(pickle.dumps((should_keys, conts))))