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
20 from social.models import UserList
23 class Poem(models.Model):
24 slug = models.SlugField('slug', max_length=120, db_index=True)
25 text = models.TextField('tekst')
26 created_by = models.ForeignKey(User, models.SET_NULL, null=True)
27 created_from = models.TextField('dodatkowe informacje', null=True, blank=True)
28 created_at = models.DateTimeField('data utworzenia', auto_now_add=True, editable=False)
29 seen_at = models.DateTimeField('data ostatniego obejrzenia', auto_now_add=True, editable=False)
30 view_count = models.IntegerField('licznik obejrzeń', 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('plik kontynuacji', 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_userlist(cls, ul):
143 cont_tabs = (cls.get(b) for b in ul.get_books())
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, UserList):
151 should_keys = set(b.pk for b in sth.get_books())
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, UserList):
166 conts = cls.for_userlist(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))))