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 catalogue.fields import JSONField
22 from catalogue.models import Book, Tag
25 class Poem(models.Model):
26 slug = models.SlugField(_('slug'), max_length=120, db_index=True)
27 text = models.TextField(_('text'))
28 created_by = models.ForeignKey(User, null=True)
29 created_from = JSONField(_('extra information'), null=True, blank=True)
30 created_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False)
31 seen_at = models.DateTimeField(_('last view date'), auto_now_add=True, editable=False)
32 view_count = models.IntegerField(_('view count'), default=1)
35 f = open(settings.LESMIANATOR_PICKLE)
36 global_dictionary = cPickle.load(f)
39 global_dictionary = {}
43 self.seen_at = datetime.now()
46 def __unicode__(self):
47 return "%s (%s...)" % (self.slug, self.text[:20])
50 def choose_letter(word, continuations):
51 if word not in continuations:
54 choices = sum((continuations[word][letter]
55 for letter in continuations[word]))
56 r = randint(0, choices - 1)
58 for letter in continuations[word]:
59 r -= continuations[word][letter]
64 def write(cls, continuations=None, length=3, min_lines=2, maxlen=1000):
65 if continuations is None:
66 continuations = cls.global_dictionary
73 finished_stanza_verses = 0
74 current_stanza_verses = 0
79 # do `min_lines' non-empty verses and then stop,
80 # but let Lesmianator finish his last stanza.
81 while finished_stanza_verses < min_lines and char_count < maxlen:
82 letter = cls.choose_letter(word, continuations)
83 letters.append(letter)
84 word = word[-length + 1:] + letter
89 finished_stanza_verses += current_stanza_verses
90 current_stanza_verses = 0
92 current_stanza_verses += 1
97 return ''.join(letters).strip()
99 def get_absolute_url(self):
100 return reverse('get_poem', kwargs={'poem': self.slug})
103 class Continuations(models.Model):
104 pickle = models.FileField(_('Continuations file'), upload_to='lesmianator')
105 content_type = models.ForeignKey(ContentType)
106 object_id = models.PositiveIntegerField()
107 content_object = generic.GenericForeignKey('content_type', 'object_id')
110 unique_together = (('content_type', 'object_id'), )
112 def __unicode__(self):
113 return "Continuations for: %s" % unicode(self.content_object)
116 def join_conts(a, b):
118 a.setdefault(pre, {})
120 a[pre].setdefault(post, 0)
121 a[pre][post] += b[pre][post]
125 def for_book(cls, book, length=3):
126 from librarian import text
128 # count from this book only
130 f = open(book.xml_file.path)
131 text.transform(f, output, False, ('raw-text',))
135 for letter in output.getvalue().decode('utf-8').strip().lower():
136 mydict = conts.setdefault(last_word, {})
137 mydict.setdefault(letter, 0)
139 last_word = last_word[-length+1:] + letter
141 return reduce(cls.join_conts,
142 (cls.get(child) for child in book.children.all()),
146 def for_set(cls, tag):
147 # book contains its descendants, we don't want them twice
148 books = Book.tagged.with_any((tag,))
149 l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books])
150 descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
152 books = books.exclude(pk__in=descendants_keys)
154 cont_tabs = (cls.get(b) for b in books)
155 return reduce(cls.join_conts, cont_tabs)
159 object_type = ContentType.objects.get_for_model(sth)
160 should_keys = set([sth.id])
161 if isinstance(sth, Tag):
162 should_keys = set(b.pk for b in Book.tagged.with_any((sth,)))
164 obj = cls.objects.get(content_type=object_type, object_id=sth.id)
166 raise cls.DoesNotExist
167 f = open(obj.pickle.path)
168 keys, conts = cPickle.load(f)
170 if set(keys) != should_keys:
171 raise cls.DoesNotExist
173 except cls.DoesNotExist:
174 if isinstance(sth, Book):
175 conts = cls.for_book(sth)
176 elif isinstance(sth, Tag):
177 conts = cls.for_set(sth)
179 raise NotImplemented('Lesmianator continuations: only Book and Tag supported')
181 c, created = cls.objects.get_or_create(content_type=object_type, object_id=sth.id)
182 c.pickle.save(sth.slug+'.p', ContentFile(cPickle.dumps((should_keys, conts))))