move some imports
[wolnelektury.git] / apps / lesmianator / models.py
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.
4 #
5 import cPickle
6 from datetime import datetime
7 from random import randint
8 from StringIO import StringIO
9
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
20
21 from catalogue.fields import JSONField
22 from catalogue.models import Book, Tag
23
24
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)
33
34     try:
35         f = open(settings.LESMIANATOR_PICKLE)
36         global_dictionary = cPickle.load(f)
37         f.close()
38     except:
39         global_dictionary = {}
40
41     def visit(self):
42         self.view_count += 1
43         self.seen_at = datetime.now()
44         self.save()
45
46     def __unicode__(self):
47         return "%s (%s...)" % (self.slug, self.text[:20])
48
49     @staticmethod
50     def choose_letter(word, continuations):
51         if word not in continuations:
52             return u'\n'
53
54         choices = sum((continuations[word][letter]
55                        for letter in continuations[word]))
56         r = randint(0, choices - 1)
57
58         for letter in continuations[word]:
59             r -= continuations[word][letter]
60             if r < 0:
61                 return letter
62
63     @classmethod
64     def write(cls, continuations=None, length=3, min_lines=2, maxlen=1000):
65         if continuations is None:
66             continuations = cls.global_dictionary
67         if not continuations:
68             return ''
69
70         letters = []
71         word = u''
72
73         finished_stanza_verses = 0
74         current_stanza_verses = 0
75         verse_start = True
76
77         char_count = 0
78
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
85             char_count += 1
86
87             if letter == u'\n':
88                 if verse_start:
89                     finished_stanza_verses += current_stanza_verses
90                     current_stanza_verses = 0
91                 else:
92                     current_stanza_verses += 1
93                     verse_start = True
94             else:
95                 verse_start = False
96
97         return ''.join(letters).strip()
98
99     def get_absolute_url(self):
100         return reverse('get_poem', kwargs={'poem': self.slug})
101
102
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')
108
109     class Meta:
110         unique_together = (('content_type', 'object_id'), )
111
112     def __unicode__(self):
113         return "Continuations for: %s" % unicode(self.content_object)
114
115     @staticmethod
116     def join_conts(a, b):
117         for pre in b:
118             a.setdefault(pre, {})
119             for post in b[pre]:
120                 a[pre].setdefault(post, 0)
121                 a[pre][post] += b[pre][post]
122         return a
123
124     @classmethod
125     def for_book(cls, book, length=3):
126         from librarian import text
127
128         # count from this book only
129         output = StringIO()
130         f = open(book.xml_file.path)
131         text.transform(f, output, False, ('raw-text',))
132         f.close()
133         conts = {}
134         last_word = ''
135         for letter in output.getvalue().decode('utf-8').strip().lower():
136             mydict = conts.setdefault(last_word, {})
137             mydict.setdefault(letter, 0)
138             mydict[letter] += 1
139             last_word = last_word[-length+1:] + letter
140         # add children
141         return reduce(cls.join_conts, 
142                       (cls.get(child) for child in book.children.all()),
143                       conts)
144
145     @classmethod
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)]
151         if descendants_keys:
152             books = books.exclude(pk__in=descendants_keys)
153
154         cont_tabs = (cls.get(b) for b in books)
155         return reduce(cls.join_conts, cont_tabs)
156
157     @classmethod
158     def get(cls, sth):
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,)))
163         try:
164             obj = cls.objects.get(content_type=object_type, object_id=sth.id)
165             if not obj.pickle:
166                 raise cls.DoesNotExist
167             f = open(obj.pickle.path)
168             keys, conts = cPickle.load(f)
169             f.close()
170             if set(keys) != should_keys:
171                 raise cls.DoesNotExist
172             return conts
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)
178             else:
179                 raise NotImplemented('Lesmianator continuations: only Book and Tag supported')
180
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))))
183             c.save()
184             return conts
185
186