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.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))))