zinnia.models
Covered: 208 lines
Missed: 12 lines
Skipped 56 lines
Percent: 94 %
  1
"""Models of Zinnia"""
  2
import warnings
  3
from datetime import datetime
  5
from django.db import models
  6
from django.db.models import Q
  7
from django.utils.html import strip_tags
  8
from django.utils.html import linebreaks
  9
from django.contrib.auth.models import User
 10
from django.contrib.sites.models import Site
 11
from django.db.models.signals import post_save
 12
from django.utils.importlib import import_module
 13
from django.contrib.comments.models import Comment
 14
from django.contrib.comments.models import CommentFlag
 15
from django.contrib.comments.moderation import moderator
 16
from django.utils.translation import ugettext_lazy as _
 18
import mptt
 19
from tagging.fields import TagField
 21
from zinnia.settings import USE_BITLY
 22
from zinnia.settings import UPLOAD_TO
 23
from zinnia.settings import ENTRY_TEMPLATES
 24
from zinnia.settings import ENTRY_BASE_MODEL
 25
from zinnia.managers import entries_published
 26
from zinnia.managers import EntryPublishedManager
 27
from zinnia.managers import AuthorPublishedManager
 28
from zinnia.managers import DRAFT, HIDDEN, PUBLISHED
 29
from zinnia.moderator import EntryCommentModerator
 30
from zinnia.signals import ping_directories_handler
 31
from zinnia.signals import ping_external_urls_handler
 34
class Author(User):
 35
    """Proxy Model around User"""
 37
    objects = models.Manager()
 38
    published = AuthorPublishedManager()
 40
    def entries_published_set(self):
 41
        """Return only the entries published"""
 42
        return entries_published(self.entry_set)
 44
    @models.permalink
 45
    def get_absolute_url(self):
 46
        """Return author's URL"""
 47
        return ('zinnia_author_detail', (self.username,))
 49
    class Meta:
 50
        """Author's Meta"""
 51
        proxy = True
 54
class Category(models.Model):
 55
    """Category object for Entry"""
 57
    title = models.CharField(_('title'), max_length=255)
 58
    slug = models.SlugField(help_text=_('used for publication'),
 59
                            unique=True, max_length=255)
 60
    description = models.TextField(_('description'), blank=True)
 62
    parent = models.ForeignKey('self', null=True, blank=True,
 63
                               verbose_name=_('parent category'),
 64
                               related_name='children')
 66
    def entries_published_set(self):
 67
        """Return only the entries published"""
 68
        return entries_published(self.entry_set)
 70
    @property
 71
    def tree_path(self):
 72
        """Return category's tree path, by his ancestors"""
 73
        if self.parent:
 74
            return '%s/%s' % (self.parent.tree_path, self.slug)
 75
        return self.slug
 77
    def __unicode__(self):
 78
        return self.title
 80
    @models.permalink
 81
    def get_absolute_url(self):
 82
        """Return category's URL"""
 83
        return ('zinnia_category_detail', (self.tree_path,))
 85
    class Meta:
 86
        """Category's Meta"""
 87
        ordering = ['title']
 88
        verbose_name = _('category')
 89
        verbose_name_plural = _('categories')
 92
class EntryAbstractClass(models.Model):
 93
    """Base Model design for publishing entries"""
 94
    STATUS_CHOICES = ((DRAFT, _('draft')),
 95
                      (HIDDEN, _('hidden')),
 96
                      (PUBLISHED, _('published')))
 98
    title = models.CharField(_('title'), max_length=255)
100
    image = models.ImageField(_('image'), upload_to=UPLOAD_TO,
101
                              blank=True, help_text=_('used for illustration'))
102
    content = models.TextField(_('content'))
103
    excerpt = models.TextField(_('excerpt'), blank=True,
104
                                help_text=_('optional element'))
106
    tags = TagField(_('tags'))
107
    categories = models.ManyToManyField(Category, verbose_name=_('categories'),
108
                                        blank=True, null=True)
109
    related = models.ManyToManyField('self', verbose_name=_('related entries'),
110
                                     blank=True, null=True)
112
    slug = models.SlugField(help_text=_('used for publication'),
113
                            unique_for_date='creation_date',
114
                            max_length=255)
116
    authors = models.ManyToManyField(User, verbose_name=_('authors'),
117
                                     blank=True, null=False)
118
    status = models.IntegerField(choices=STATUS_CHOICES, default=DRAFT)
119
    featured = models.BooleanField(_('featured'), default=False)
120
    comment_enabled = models.BooleanField(_('comment enabled'), default=True)
121
    pingback_enabled = models.BooleanField(_('linkback enabled'), default=True)
123
    creation_date = models.DateTimeField(_('creation date'), default=datetime.now)
124
    last_update = models.DateTimeField(_('last update'), default=datetime.now)
125
    start_publication = models.DateTimeField(_('start publication'),
126
                                             help_text=_('date start publish'),
127
                                             default=datetime.now)
128
    end_publication = models.DateTimeField(_('end publication'),
129
                                           help_text=_('date end publish'),
130
                                           default=datetime(2042, 3, 15))
132
    sites = models.ManyToManyField(Site, verbose_name=_('sites publication'))
134
    login_required = models.BooleanField(_('login required'), default=False,
135
                                         help_text=_('only authenticated users can view the entry'))
136
    password = models.CharField(_('password'), max_length=50, blank=True,
137
                                help_text=_('protect the entry with a password'))
139
    template = models.CharField(_('template'), max_length=250,
140
                                default='zinnia/entry_detail.html',
141
                                choices=[('zinnia/entry_detail.html',
142
                                          _('Default template'))] +
143
                                ENTRY_TEMPLATES,
144
                                help_text=_('template used to display the entry'))
146
    objects = models.Manager()
147
    published = EntryPublishedManager()
149
    @property
150
    def html_content(self):
151
        """Return the content correctly formatted"""
152
        if not '</p>' in self.content:
153
            return linebreaks(self.content)
154
        return self.content
156
    @property
157
    def previous_entry(self):
158
        """Return the previous entry"""
159
        entries = Entry.published.filter(
160
            creation_date__lt=self.creation_date)[:1]
161
        if entries:
162
            return entries[0]
164
    @property
165
    def next_entry(self):
166
        """Return the next entry"""
167
        entries = Entry.published.filter(
168
            creation_date__gt=self.creation_date).order_by('creation_date')[:1]
169
        if entries:
170
            return entries[0]
172
    @property
173
    def word_count(self):
174
        """Count the words of an entry"""
175
        return len(strip_tags(self.html_content).split())
177
    @property
178
    def is_actual(self):
179
        """Check if an entry is within publication period"""
180
        now = datetime.now()
181
        return now >= self.start_publication and now < self.end_publication
183
    @property
184
    def is_visible(self):
185
        """Check if an entry is visible on site"""
186
        return self.is_actual and self.status == PUBLISHED
188
    @property
189
    def related_published_set(self):
190
        """Return only related entries published"""
191
        return entries_published(self.related)
193
    @property
194
    def discussions(self):
195
        """Return published discussions"""
196
        return Comment.objects.for_model(self).filter(is_public=True)
198
    @property
199
    def comments(self):
200
        """Return published comments"""
201
        return self.discussions.filter(Q(flags=None) | Q(
202
            flags__flag=CommentFlag.MODERATOR_APPROVAL))
204
    @property
205
    def pingbacks(self):
206
        """Return published pingbacks"""
207
        return self.discussions.filter(flags__flag='pingback')
209
    @property
210
    def trackbacks(self):
211
        """Return published trackbacks"""
212
        return self.discussions.filter(flags__flag='trackback')
214
    @property
215
    def short_url(self):
216
        """Return the entry's short url"""
217
        if not USE_BITLY:
218
            return False
220
        from django_bitly.models import Bittle
222
        bittle = Bittle.objects.bitlify(self)
223
        url = bittle and bittle.shortUrl or self.get_absolute_url()
224
        return url
226
    def __unicode__(self):
227
        return '%s: %s' % (self.title, self.get_status_display())
229
    @models.permalink
230
    def get_absolute_url(self):
231
        """Return entry's URL"""
232
        return ('zinnia_entry_detail', (), {
233
            'year': self.creation_date.strftime('%Y'),
234
            'month': self.creation_date.strftime('%m'),
235
            'day': self.creation_date.strftime('%d'),
236
            'slug': self.slug})
238
    class Meta:
239
        abstract = True
242
def get_base_model():
243
    """Determine the base Model to inherit in the
244
    Entry Model, this allow to overload it."""
245
    if not ENTRY_BASE_MODEL:
246
        return EntryAbstractClass
248
    dot = ENTRY_BASE_MODEL.rindex('.')
249
    module_name, class_name = ENTRY_BASE_MODEL[:dot], ENTRY_BASE_MODEL[dot + 1:]
250
    try:
251
        _class = getattr(import_module(module_name), class_name)
252
        return _class
253
    except (ImportError, AttributeError):
254
        warnings.warn('%s cannot be imported' % ENTRY_BASE_MODEL, RuntimeWarning)
255
    return EntryAbstractClass
258
class Entry(get_base_model()):
259
    """Final Entry model"""
261
    class Meta:
262
        """Entry's Meta"""
263
        ordering = ['-creation_date']
264
        verbose_name = _('entry')
265
        verbose_name_plural = _('entries')
266
        permissions = (('can_view_all', 'Can view all'),
267
                       ('can_change_author', 'Can change author'), )
270
moderator.register(Entry, EntryCommentModerator)
271
mptt.register(Category, order_insertion_by=['title'])
272
post_save.connect(ping_directories_handler, sender=Entry,
273
                  dispatch_uid='zinnia.entry.post_save.ping_directories')
274
post_save.connect(ping_external_urls_handler, sender=Entry,
275
                  dispatch_uid='zinnia.entry.post_save.ping_external_urls')