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