1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
6 from datetime import datetime
7 from random import randint
8 from StringIO import StringIO
10 from django.core.files.base import ContentFile
11 from django.db import models
12 from django.db.models import permalink
13 from django.utils.translation import ugettext_lazy as _
14 from django.core.urlresolvers import reverse
15 from django.db.models.signals import m2m_changed
16 from django.contrib.auth.models import User
17 from django.contrib.contenttypes.models import ContentType
18 from django.contrib.contenttypes import generic
19 from django.conf import settings
21 from librarian import text
22 from catalogue.fields import JSONField
23 from catalogue.models import Book, Tag
26 class Poem(models.Model):
27 slug = models.SlugField(_('slug'), max_length=120, db_index=True)
28 text = models.TextField(_('text'))
29 created_by = models.ForeignKey(User, null=True)
30 created_from = JSONField(_('extra information'), null=True, blank=True)
31 created_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False)
32 seen_at = models.DateTimeField(_('last view date'), auto_now_add=True, editable=False)
33 view_count = models.IntegerField(_('view count'), default=1)
36 f = open(settings.LESMIANATOR_PICKLE)
37 global_dictionary = cPickle.load(f)
40 global_dictionary = {}
44 self.seen_at = datetime.now()
47 def __unicode__(self):
48 return "%s (%s...)" % (self.slug, self.text[:20])
51 def choose_letter(word, continuations):
52 if word not in continuations:
55 choices = sum((continuations[word][letter]
56 for letter in continuations[word]))
57 r = randint(0, choices - 1)
59 for letter in continuations[word]:
60 r -= continuations[word][letter]
65 def write(cls, continuations=None, length=3, min_lines=2, maxlen=1000):
66 if continuations is None:
67 continuations = cls.global_dictionary
74 finished_stanza_verses = 0
75 current_stanza_verses = 0
80 # do `min_lines' non-empty verses and then stop,
81 # but let Lesmianator finish his last stanza.
82 while finished_stanza_verses < min_lines and char_count < maxlen:
83 letter = cls.choose_letter(word, continuations)
84 letters.append(letter)
85 word = word[-length + 1:] + letter
90 finished_stanza_verses += current_stanza_verses
91 current_stanza_verses = 0
93 current_stanza_verses += 1
98 return ''.join(letters).strip()
100 def get_absolute_url(self):
101 return reverse('get_poem', kwargs={'poem': self.slug})
104 class Continuations(models.Model):
105 pickle = models.FileField(_('Continuations file'), upload_to='lesmianator')
106 content_type = models.ForeignKey(ContentType)
107 object_id = models.PositiveIntegerField()
108 content_object = generic.GenericForeignKey('content_type', 'object_id')
111 unique_together = (('content_type', 'object_id'), )
113 def __unicode__(self):
114 return "Continuations for: %s" % unicode(self.content_object)
117 def join_conts(a, b):
119 a.setdefault(pre, {})
121 a[pre].setdefault(post, 0)
122 a[pre][post] += b[pre][post]
126 def for_book(cls, book, length=3):
127 # count from this book only
129 f = open(book.xml_file.path)
130 text.transform(f, output, False, ('raw-text',))
134 for letter in output.getvalue().decode('utf-8').strip().lower():
135 mydict = conts.setdefault(last_word, {})
136 mydict.setdefault(letter, 0)
138 last_word = last_word[-length+1:] + letter
140 return reduce(cls.join_conts,
141 (cls.get(child) for child in book.children.all()),
145 def for_set(cls, tag):
146 # book contains its descendants, we don't want them twice
147 books = Book.tagged.with_any((tag,))
148 l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books])
149 descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
151 books = books.exclude(pk__in=descendants_keys)
153 cont_tabs = (cls.get(b) for b in books)
154 return reduce(cls.join_conts, cont_tabs)
158 object_type = ContentType.objects.get_for_model(sth)
159 should_keys = set([sth.id])
160 if isinstance(sth, Tag):
161 should_keys = set(b.pk for b in Book.tagged.with_any((sth,)))
163 obj = cls.objects.get(content_type=object_type, object_id=sth.id)
165 raise cls.DoesNotExist
166 f = open(obj.pickle.path)
167 keys, conts = cPickle.load(f)
169 if set(keys) != should_keys:
170 raise cls.DoesNotExist
172 except cls.DoesNotExist:
173 if isinstance(sth, Book):
174 conts = cls.for_book(sth)
175 elif isinstance(sth, Tag):
176 conts = cls.for_set(sth)
178 raise NotImplemented('Lesmianator continuations: only Book and Tag supported')
180 c, created = cls.objects.get_or_create(content_type=object_type, object_id=sth.id)
181 c.pickle.save(sth.slug+'.p', ContentFile(cPickle.dumps((should_keys, conts))))