added model inheritance,
[redakcja.git] / apps / wiki / models.py
1 # -*- coding: utf-8 -*-
2 #
3 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
4 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
5 #
6 import itertools
7 import re
8
9 from django.core.urlresolvers import reverse
10 from django.db import models
11 from django.utils.translation import ugettext_lazy as _
12
13 from dvcs import models as dvcs_models
14
15
16 import logging
17 logger = logging.getLogger("fnp.wiki")
18
19
20 RE_TRIM_BEGIN = re.compile("^<!-- TRIM_BEGIN -->$", re.M)
21 RE_TRIM_END = re.compile("^<!-- TRIM_END -->$", re.M)
22
23
24 class Book(models.Model):
25     """ A document edited on the wiki """
26
27     title = models.CharField(_('title'), max_length=255)
28     slug = models.SlugField(_('slug'), max_length=128, unique=True)
29     gallery = models.CharField(_('scan gallery name'), max_length=255, blank=True)
30
31     parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children")
32     parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True)
33
34     class Meta:
35         ordering = ['parent_number', 'title']
36         verbose_name = _('book')
37         verbose_name_plural = _('books')
38
39     def __unicode__(self):
40         return self.title
41
42     @classmethod
43     def create(cls, creator=None, text=u'', *args, **kwargs):
44         """
45             >>> Book.create(slug='x', text='abc').materialize()
46             'abc'
47         """
48         instance = cls(*args, **kwargs)
49         instance.save()
50         instance[0].commit(author=creator, text=text)
51         return instance
52
53     def __iter__(self):
54         return iter(self.chunk_set.all())
55
56     def __getitem__(self, chunk):
57         return self.chunk_set.all()[chunk]
58
59     def __len__(self):
60         return self.chunk_set.count()
61
62     @staticmethod
63     def trim(text, trim_begin=True, trim_end=True):
64         """ 
65             Cut off everything before RE_TRIM_BEGIN and after RE_TRIM_END, so
66             that eg. one big XML file can be compiled from many small XML files.
67         """
68         if trim_begin:
69             text = RE_TRIM_BEGIN.split(text, maxsplit=1)[-1]
70         if trim_end:
71             text = RE_TRIM_END.split(text, maxsplit=1)[0]
72         return text
73
74     def materialize(self):
75         """ 
76             Get full text of the document compiled from chunks.
77             Takes the current versions of all texts for now, but it should
78             be possible to specify a tag or a point in time for compiling.
79
80             First non-empty text's beginning isn't trimmed,
81             and last non-empty text's end isn't trimmed.
82         """
83         texts = []
84         trim_begin = False
85         text = ''
86         for chunk in self:
87             next_text = chunk.materialize()
88             if not next_text:
89                 continue
90             if text:
91                 # trim the end, because there's more non-empty text
92                 # don't trim beginning, if `text' is the first non-empty part
93                 texts.append(self.trim(text, trim_begin=trim_begin))
94                 trim_begin = True
95             text = next_text
96         # don't trim the end, because there's no more text coming after `text'
97         # only trim beginning if it's not still the first non-empty
98         texts.append(self.trim(text, trim_begin=trim_begin, trim_end=False))
99         return "".join(texts)
100
101     @staticmethod
102     def listener_create(sender, instance, created, **kwargs):
103         if created:
104             instance.chunk_set.create(number=1, slug='1')
105
106 models.signals.post_save.connect(Book.listener_create, sender=Book)
107
108
109 class Chunk(dvcs_models.Document):
110     """ An editable chunk of text. Every Book text is divided into chunks. """
111
112     book = models.ForeignKey(Book)
113     number = models.IntegerField()
114     slug = models.SlugField()
115     comment = models.CharField(max_length=255)
116
117     class Meta:
118         unique_together = [['book', 'number'], ['book', 'slug']]
119         ordering = ['number']
120
121     def __unicode__(self):
122         return "%d-%d: %s" % (self.book_id, self.number, self.comment)
123
124     def get_absolute_url(self):
125         return reverse("wiki_editor", args=[self.book.slug, self.slug])
126
127     @classmethod
128     def get(cls, slug, chunk=None):
129         if chunk is None:
130             return cls.objects.get(book__slug=slug, number=1)
131         else:
132             return cls.objects.get(book__slug=slug, slug=chunk)
133
134     def pretty_name(self):
135         return "%s, %s (%d/%d)" % (self.book.title, self.comment, 
136                 self.number, len(self.book))
137
138
139
140
141 '''
142 from wiki import settings, constants
143 from slughifi import slughifi
144
145 from django.http import Http404
146
147
148
149
150 class Document(object):
151
152     def add_tag(self, tag, revision, author):
153         """ Add document specific tag """
154         logger.debug("Adding tag %s to doc %s version %d", tag, self.name, revision)
155         self.storage.vstorage.add_page_tag(self.name, revision, tag, user=author)
156
157     @property
158     def plain_text(self):
159         return re.sub(self.META_REGEX, '', self.text, 1)
160
161     def meta(self):
162         result = {}
163
164         m = re.match(self.META_REGEX, self.text)
165         if m:
166             for line in m.group(1).split('\n'):
167                 try:
168                     k, v = line.split(':', 1)
169                     result[k.strip()] = v.strip()
170                 except ValueError:
171                     continue
172
173         gallery = result.get('gallery', slughifi(self.name.replace(' ', '_')))
174
175         if gallery.startswith('/'):
176             gallery = os.path.basename(gallery)
177
178         result['gallery'] = gallery
179         return result
180
181     def info(self):
182         return self.storage.vstorage.page_meta(self.name, self.revision)
183
184
185
186 '''
187 class Theme(models.Model):
188     name = models.CharField(_('name'), max_length=50, unique=True)
189
190     class Meta:
191         ordering = ('name',)
192         verbose_name = _('theme')
193         verbose_name_plural = _('themes')
194
195     def __unicode__(self):
196         return self.name
197
198     def __repr__(self):
199         return "Theme(name=%r)" % self.name
200