Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: Fantomas42/django-blog-zinnia
base: 3c80a5ca42
...
head fork: Fantomas42/django-blog-zinnia
compare: f684f9f0b8
  • 7 commits
  • 11 files changed
  • 0 commit comments
  • 1 contributor
View
3  docs/extensions/zinnia_docs.py
@@ -12,7 +12,8 @@
def skip_model_member(app, what, name, obj, skip, options):
# These fields always fails !
- if name in ('tags', 'image'):
+ if name in ('tags', 'image',
+ 'next_entry', 'previous_entry'):
return True
return skip
View
109 docs/how-to/extending_entry_model.rst
@@ -42,48 +42,57 @@ The extension process is done in three main steps:
#. Register your class into Zinnia to be used.
#. Update the :class:`~zinnia.admin.entry.EntryAdmin` class accordingly.
-In the suite of this document we will show how to add an image gallery into
-the :class:`Entry` model to illustrate the concepts involved. We assume that
-the pieces of codes written for this document belong in the
-:mod:`zinnia_gallery` package/application.
-
.. _writing-model-extension:
Writing model extension
=======================
+In the suite of this document we will show how to add an image gallery into
+the :class:`Entry` model to illustrate the concepts involved when
+extending. We assume that the pieces of codes written for this document
+belong in the :mod:`zinnia_gallery` module/application.
+
+.. versionchanged:: 0.13
+
+The :class:`zinnia.models.entry.EntryAbstractClass` has been moved and
+renamed to :class:`zinnia.models_bases.entry.AbstractEntry`.
+
The first step to extend the :class:`Entry` model is to define a new class
-inherited from the :class:`EntryAbstractClass` and add your fields or/and
-override the inherited methods if needed. So in :mod:`zinnia_gallery` let's
-write our new class in a file named :file:`entry_gallery.py`. ::
+inherited from the :class:`~zinnia.models_bases.entry.AbstractEntry` and
+add your fields or/and override the inherited methods if needed. So in
+:mod:`zinnia_gallery` let's write our gallery models and the extension in
+the :class:`Entry` model in :file:`models.py`. ::
from django.db import models
- from zinnia_gallery.models import Gallery
- from zinnia.models.entry import EntryAbstractClass
+ from zinnia.models_bases.entry import AbstractEntry
- class EntryGallery(EntryAbstractClass):
+ class Picture(models.Model):
+ title = models.CharField(max_length=50)
+ image = models.ImageField(upload_to='gallery')
+
+ class Gallery(models.Model):
+ title = models.CharField(max_length=50)
+ pictures = models.ManyToManyField(Picture)
+
+ class EntryGallery(AbstractEntry):
gallery = models.ForeignKey(Gallery)
def __unicode__(self):
return u'EntryGallery %s' % self.title
- class Meta(EntryAbstractClass.Meta):
+ class Meta(AbstractEntry.Meta):
abstract = True
-In this code sample, we add a new :class:`~django.db.models.ForeignKey`
-field named ``gallery`` pointing to a :class:`Gallery` model defined in
-:mod:`zinnia_gallery.models` and we override the
-:meth:`EntryAbstractClass.__unicode__` method.
+In this code sample, we simply add in our :class:`Entry` model a new
+:class:`~django.db.models.ForeignKey` field named ``gallery`` pointing to a
+:class:`Gallery` model and we override the :meth:`Entry.__unicode__` method.
-.. note:: You have to respect **3 important rules** to make extending working :
+.. note:: You have to respect **2 important rules** to make extending working :
#. Do not import the :class:`Entry` model in your file defining
the extended model because it will cause a circular
importation.
- #. Do not put your abstract model in a file named :file:`models.py`,
- it will not work for a non obvious reason.
-
#. Don't forget to tell that your model is ``abstract``. Otherwise a
table will be created and the extending process will not work
as expected.
@@ -92,10 +101,66 @@ field named ``gallery`` pointing to a :class:`Gallery` model defined in
:ref:`model-inheritance` for more information about the concepts
behind the model inheritence in Django and the limitations.
+.. _writing-model-customisation:
+
+Writing model customisation
+===========================
+
+Adding fields is pretty easy, but now that the :class:`Entry` model has
+been extended, we want to change the :attr:`image` field wich is an
+:class:`~django.db.models.ImageField` by default to use our new
+:class:`Picture` instead.
+
+To customise this field, the same process as extending apply, but we can
+take advantage of all the abstracts classes provided to build the
+:class:`~zinnia.models_bases.entry.AbstractEntry` to rebuild our own custom
+:class:`Entry` model like this: ::
+
+ from django.db import models
+ from zinnia.models_bases import entry
+
+ class Picture(models.Model):
+ title = models.CharField(max_length=50)
+ image = models.ImageField(upload_to='gallery')
+
+ class Gallery(models.Model):
+ title = models.CharField(max_length=50)
+ pictures = models.ManyToManyField(Picture)
+
+ class EntryGallery(
+ entry.CoreEntry,
+ entry.ContentEntry,
+ entry.DiscussionsEntry,
+ entry.RelatedEntry,
+ entry.ExcerptEntry,
+ entry.FeaturedEntry,
+ entry.AuthorsEntry,
+ entry.CategoriesEntry,
+ entry.TagsEntry,
+ entry.LoginRequiredEntry,
+ entry.PasswordRequiredEntry,
+ entry.ContentTemplateEntry,
+ entry.DetailTemplateEntry):
+
+ image = models.ForeignKey(Picture)
+ gallery = models.ForeignKey(Gallery)
+
+ def __unicode__(self):
+ return u'EntryGallery %s' % self.title
+
+ class Meta(entry.CoreEntry.Meta):
+ abstract = True
+
+Now we have an :class:`Entry` model extended with a gallery of pictures and
+customised with a :class:`Picture` model relation as the :attr:`image`
+field.
+
+Note that the same process apply if you want to delete some built-in fields.
+
.. _database-considerations:
Considerations about the database
----------------------------------
+=================================
If you do the extension of the :class:`Entry` model after the ``syncdb``
command, you have to manually alter the Zinnia's tables for reflecting your
@@ -133,7 +198,7 @@ process.
Following our example we must add this line in the project's settings. ::
- ZINNIA_ENTRY_BASE_MODEL = 'zinnia_gallery.entry_gallery.EntryGallery'
+ ZINNIA_ENTRY_BASE_MODEL = 'zinnia_gallery.models.EntryGallery'
If an error occurs when your new class is imported a warning will be raised
and the :class:`EntryAbstractClass` will be used.
View
7 docs/how-to/rewriting_entry_url.rst
@@ -90,9 +90,10 @@ build the canonical URL of an entry.
To do this override, simply use the method explained in the
:doc:`/how-to/extending_entry_model` document to create a new class based on
-:class:`EntryAbstractClass` with the new ``get_absolute_url`` method. ::
+:class:`~zinnia.models_bases.entry.AbstractEntry` with the new
+``get_absolute_url`` method. ::
- class EntryWithNewUrl(EntryAbstractClass):
+ class EntryWithNewUrl(AbstractEntry):
"""Entry with '/blog/<id>/' URL"""
@models.permalink
@@ -100,7 +101,7 @@ To do this override, simply use the method explained in the
return ('zinnia_entry_detail', (),
{'pk': self.id})
- class Meta(EntryAbstractClass.Meta):
+ class Meta(AbstractEntry.Meta):
abstract = True
Due to the intensive use of this method into the templates, make sure that
View
20 docs/ref/api/zinnia.models_bases.rst
@@ -0,0 +1,20 @@
+models_bases Package
+====================
+
+:mod:`models_bases` Package
+---------------------------
+
+.. automodule:: zinnia.models_bases
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`entry` Module
+-------------------
+
+.. automodule:: zinnia.models_bases.entry
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
View
1  docs/ref/api/zinnia.rst
@@ -90,6 +90,7 @@ Subpackages
zinnia.admin
zinnia.models
+ zinnia.models_bases
zinnia.spam_checker
zinnia.templatetags
zinnia.url_shortener
View
478 zinnia/models/entry.py
@@ -1,482 +1,10 @@
"""Entry model for Zinnia"""
import warnings
-from django.db import models
-from django.db.models import Q
-from django.utils import timezone
-from django.utils.html import strip_tags
-from django.utils.html import linebreaks
-from django.contrib.sites.models import Site
from django.utils.importlib import import_module
-from django.utils.functional import cached_property
-from django.contrib import comments
-from django.contrib.comments.models import CommentFlag
-from django.utils.translation import ugettext_lazy as _
-from django.contrib.markup.templatetags.markup import markdown
-from django.contrib.markup.templatetags.markup import textile
-from django.contrib.markup.templatetags.markup import restructuredtext
-
-from tagging.fields import TagField
-from tagging.utils import parse_tag_input
-
-from zinnia.models.author import Author
-from zinnia.models.category import Category
-from zinnia.flags import PINGBACK, TRACKBACK
-from zinnia.settings import UPLOAD_TO
-from zinnia.settings import MARKUP_LANGUAGE
-from zinnia.settings import MARKDOWN_EXTENSIONS
from zinnia.settings import ENTRY_BASE_MODEL
-from zinnia.settings import ENTRY_DETAIL_TEMPLATES
-from zinnia.settings import ENTRY_CONTENT_TEMPLATES
-from zinnia.settings import AUTO_CLOSE_COMMENTS_AFTER
-from zinnia.settings import AUTO_CLOSE_PINGBACKS_AFTER
-from zinnia.settings import AUTO_CLOSE_TRACKBACKS_AFTER
-from zinnia.managers import entries_published
-from zinnia.managers import EntryPublishedManager
-from zinnia.managers import DRAFT, HIDDEN, PUBLISHED
-from zinnia.url_shortener import get_url_shortener
-
-
-class CoreEntry(models.Model):
- """
- Abstract core entry model class providing
- the fields and methods required for publishing
- content over time.
- """
- STATUS_CHOICES = ((DRAFT, _('draft')),
- (HIDDEN, _('hidden')),
- (PUBLISHED, _('published')))
-
- title = models.CharField(
- _('title'), max_length=255)
-
- slug = models.SlugField(
- _('slug'), max_length=255,
- unique_for_date='creation_date',
- help_text=_("Used to build the entry's URL."))
-
- status = models.IntegerField(
- _('status'), choices=STATUS_CHOICES, default=DRAFT)
-
- start_publication = models.DateTimeField(
- _('start publication'), blank=True, null=True,
- help_text=_('Start date of publication.'))
-
- end_publication = models.DateTimeField(
- _('end publication'), blank=True, null=True,
- help_text=_('End date of publication.'))
-
- sites = models.ManyToManyField(
- Site,
- related_name='entries',
- verbose_name=_('sites'),
- help_text=_('Sites where the entry will be published.'))
-
- creation_date = models.DateTimeField(
- _('creation date'), default=timezone.now,
- help_text=_("Used to build the entry's URL."))
-
- last_update = models.DateTimeField(
- _('last update'), default=timezone.now)
-
- objects = models.Manager()
- published = EntryPublishedManager()
-
- @property
- def is_actual(self):
- """
- Checks if an entry is within his publication period.
- """
- now = timezone.now()
- if self.start_publication and now < self.start_publication:
- return False
-
- if self.end_publication and now >= self.end_publication:
- return False
- return True
-
- @property
- def is_visible(self):
- """
- Checks if an entry is visible and published.
- """
- return self.is_actual and self.status == PUBLISHED
-
- @cached_property
- def previous_entry(self):
- """
- Returns the previous published entry if exists.
- """
- entries = Entry.published.filter(
- creation_date__lt=self.creation_date)[:1]
- if entries:
- return entries[0]
-
- @cached_property
- def next_entry(self):
- """
- Returns the next published entry if exists.
- """
- entries = Entry.published.filter(
- creation_date__gt=self.creation_date).order_by('creation_date')[:1]
- if entries:
- return entries[0]
-
- @property
- def short_url(self):
- """
- Returns the entry's short url.
- """
- return get_url_shortener()(self)
-
- @models.permalink
- def get_absolute_url(self):
- """
- Builds and returns the entry's URL based on
- the slug and the creation date.
- """
- creation_date = timezone.localtime(self.creation_date)
- return ('zinnia_entry_detail', (), {
- 'year': creation_date.strftime('%Y'),
- 'month': creation_date.strftime('%m'),
- 'day': creation_date.strftime('%d'),
- 'slug': self.slug})
-
- def __unicode__(self):
- return u'%s: %s' % (self.title, self.get_status_display())
-
- class Meta:
- """
- CoreEntry's meta informations.
- """
- abstract = True
- app_label = 'zinnia'
- ordering = ['-creation_date']
- get_latest_by = 'creation_date'
- verbose_name = _('entry')
- verbose_name_plural = _('entries')
- index_together = [['slug', 'creation_date']]
- permissions = (('can_view_all', 'Can view all entries'),
- ('can_change_status', 'Can change status'),
- ('can_change_author', 'Can change author(s)'), )
-
-
-class ContentEntry(models.Model):
- """
- Abstract content model class providing field
- and methods to write content inside an entry.
- """
- content = models.TextField(_('content'), blank=True)
-
- @property
- def html_content(self):
- """
- Returns the "content" field formatted in HTML.
- """
- if MARKUP_LANGUAGE == 'markdown':
- return markdown(self.content, MARKDOWN_EXTENSIONS)
- elif MARKUP_LANGUAGE == 'textile':
- return textile(self.content)
- elif MARKUP_LANGUAGE == 'restructuredtext':
- return restructuredtext(self.content)
- elif not '</p>' in self.content:
- return linebreaks(self.content)
- return self.content
-
- @property
- def word_count(self):
- """
- Counts the number of words used in the content.
- """
- return len(strip_tags(self.html_content).split())
-
- class Meta:
- abstract = True
-
-
-class DiscussionsEntry(models.Model):
- """
- Abstract discussion model class providing
- the fields and methods to manage the discussions
- (comments, pingbacks, trackbacks).
- """
- comment_enabled = models.BooleanField(
- _('comments enabled'), default=True,
- help_text=_('Allows comments if checked.'))
- pingback_enabled = models.BooleanField(
- _('pingbacks enabled'), default=True,
- help_text=_('Allows pingbacks if checked.'))
- trackback_enabled = models.BooleanField(
- _('trackbacks enabled'), default=True,
- help_text=_('Allows trackbacks if checked.'))
-
- comment_count = models.IntegerField(
- _('comment count'), default=0)
- pingback_count = models.IntegerField(
- _('pingback count'), default=0)
- trackback_count = models.IntegerField(
- _('trackback count'), default=0)
-
- @property
- def discussions(self):
- """
- Returns a queryset of the published discussions.
- """
- return comments.get_model().objects.for_model(
- self).filter(is_public=True, is_removed=False)
-
- @property
- def comments(self):
- """
- Returns a queryset of the published comments.
- """
- return self.discussions.filter(Q(flags=None) | Q(
- flags__flag=CommentFlag.MODERATOR_APPROVAL))
-
- @property
- def pingbacks(self):
- """
- Returns a queryset of the published pingbacks.
- """
- return self.discussions.filter(flags__flag=PINGBACK)
-
- @property
- def trackbacks(self):
- """
- Return a queryset of the published trackbacks.
- """
- return self.discussions.filter(flags__flag=TRACKBACK)
-
- def discussion_is_still_open(self, discussion_type, auto_close_after):
- """
- Checks if a type of discussion is still open
- are a certain number of days.
- """
- discussion_enabled = getattr(self, discussion_type)
- if (discussion_enabled and isinstance(auto_close_after, int)
- and auto_close_after >= 0):
- return (timezone.now() - (
- self.start_publication or self.creation_date)).days < \
- auto_close_after
- return discussion_enabled
-
- @property
- def comments_are_open(self):
- """
- Checks if the comments are open with the
- AUTO_CLOSE_COMMENTS_AFTER setting.
- """
- return self.discussion_is_still_open(
- 'comment_enabled', AUTO_CLOSE_COMMENTS_AFTER)
-
- @property
- def pingbacks_are_open(self):
- """
- Checks if the pingbacks are open with the
- AUTO_CLOSE_PINGBACKS_AFTER setting.
- """
- return self.discussion_is_still_open(
- 'pingback_enabled', AUTO_CLOSE_PINGBACKS_AFTER)
-
- @property
- def trackbacks_are_open(self):
- """
- Checks if the trackbacks are open with the
- AUTO_CLOSE_TRACKBACKS_AFTER setting.
- """
- return self.discussion_is_still_open(
- 'trackback_enabled', AUTO_CLOSE_TRACKBACKS_AFTER)
-
- class Meta:
- abstract = True
-
-
-class RelatedEntry(models.Model):
- """
- Abstract model class for making manual relations
- between the differents entries.
- """
- related = models.ManyToManyField(
- 'self',
- blank=True, null=True,
- verbose_name=_('related entries'))
-
- @property
- def related_published(self):
- """
- Returns only related entries published.
- """
- return entries_published(self.related)
-
- class Meta:
- abstract = True
-
-
-class ExcerptEntry(models.Model):
- """
- Abstract model class to add an excerpt to the entries.
- """
- excerpt = models.TextField(
- _('excerpt'), blank=True,
- help_text=_('Optional element.'))
-
- class Meta:
- abstract = True
-
-
-class ImageEntry(models.Model):
- """
- Abstract model class to add an image to the entries.
- """
- image = models.ImageField(
- _('image'), blank=True, upload_to=UPLOAD_TO,
- help_text=_('Used for illustration.'))
-
- class Meta:
- abstract = True
-
-
-class FeaturedEntry(models.Model):
- """
- Abstract model class to mark entries as featured.
- """
- featured = models.BooleanField(
- _('featured'), default=False)
-
- class Meta:
- abstract = True
-
-
-class AuthorsEntry(models.Model):
- """
- Abstract model class to add relationship
- between the entries and their authors.
- """
- authors = models.ManyToManyField(
- Author,
- related_name='entries',
- blank=True, null=False,
- verbose_name=_('authors'))
-
- class Meta:
- abstract = True
-
-
-class CategoriesEntry(models.Model):
- """
- Abstract model class to categorize the entries.
- """
- categories = models.ManyToManyField(
- Category,
- related_name='entries',
- blank=True, null=True,
- verbose_name=_('categories'))
-
- class Meta:
- abstract = True
-
-
-class TagsEntry(models.Model):
- """
- Abstract lodel class to add tags to the entries.
- """
- tags = TagField(_('tags'))
-
- @property
- def tags_list(self):
- """
- Return iterable list of tags.
- """
- return parse_tag_input(self.tags)
-
- class Meta:
- abstract = True
-
-
-class LoginRequiredEntry(models.Model):
- """
- Abstract model class to restrcit the display
- of the entry on authenticated users.
- """
- login_required = models.BooleanField(
- _('login required'), default=False,
- help_text=_('Only authenticated users can view the entry.'))
-
- class Meta:
- abstract = True
-
-
-class PasswordRequiredEntry(models.Model):
- """
- Abstract model class to restrict the display
- of the entry to users knowing the password.
- """
- password = models.CharField(
- _('password'), max_length=50, blank=True,
- help_text=_('Protects the entry with a password.'))
-
- class Meta:
- abstract = True
-
-
-class ContentTemplateEntry(models.Model):
- """
- Abstract model class to display entry's content
- with a custom template.
- """
- content_template = models.CharField(
- _('content template'), max_length=250,
- default='zinnia/_entry_detail.html',
- choices=[('zinnia/_entry_detail.html', _('Default template'))] +
- ENTRY_CONTENT_TEMPLATES,
- help_text=_("Template used to display the entry's content."))
-
- class Meta:
- abstract = True
-
-
-class DetailTemplateEntry(models.Model):
- """
- Abstract model class to display entries with a
- custom template if needed on the detail page.
- """
- detail_template = models.CharField(
- _('detail template'), max_length=250,
- default='entry_detail.html',
- choices=[('entry_detail.html', _('Default template'))] +
- ENTRY_DETAIL_TEMPLATES,
- help_text=_("Template used to display the entry's detail page."))
-
- class Meta:
- abstract = True
-
-
-class EntryAbstractClass(
- CoreEntry,
- ContentEntry,
- DiscussionsEntry,
- RelatedEntry,
- ExcerptEntry,
- ImageEntry,
- FeaturedEntry,
- AuthorsEntry,
- CategoriesEntry,
- TagsEntry,
- LoginRequiredEntry,
- PasswordRequiredEntry,
- ContentTemplateEntry,
- DetailTemplateEntry):
- """
- Final abstract entry model class assembling
- all the abstract entry model classes into a single one.
-
- In this manner we can override some fields without
- reimplemting all the EntryAbstractClass.
- """
-
- class Meta(CoreEntry.Meta):
- abstract = True
+from zinnia.models_bases.entry import AbstractEntry
def get_entry_base_model():
@@ -487,7 +15,7 @@ def get_entry_base_model():
the Entry model class.
"""
if not ENTRY_BASE_MODEL:
- return EntryAbstractClass
+ return AbstractEntry
dot = ENTRY_BASE_MODEL.rindex('.')
module_name = ENTRY_BASE_MODEL[:dot]
@@ -498,7 +26,7 @@ def get_entry_base_model():
except (ImportError, AttributeError):
warnings.warn('%s cannot be imported' % ENTRY_BASE_MODEL,
RuntimeWarning)
- return EntryAbstractClass
+ return AbstractEntry
class Entry(get_entry_base_model()):
View
1  zinnia/models_bases/__init__.py
@@ -0,0 +1 @@
+"""Base models for Zinnia"""
View
473 zinnia/models_bases/entry.py
@@ -0,0 +1,473 @@
+"""Base entry models for Zinnia"""
+from django.db import models
+from django.db.models import Q
+from django.utils import timezone
+from django.utils.html import strip_tags
+from django.utils.html import linebreaks
+from django.contrib.sites.models import Site
+from django.utils.functional import cached_property
+from django.contrib import comments
+from django.contrib.comments.models import CommentFlag
+from django.utils.translation import ugettext_lazy as _
+
+from django.contrib.markup.templatetags.markup import markdown
+from django.contrib.markup.templatetags.markup import textile
+from django.contrib.markup.templatetags.markup import restructuredtext
+
+from tagging.fields import TagField
+from tagging.utils import parse_tag_input
+
+from zinnia.flags import PINGBACK, TRACKBACK
+from zinnia.settings import UPLOAD_TO
+from zinnia.settings import MARKUP_LANGUAGE
+from zinnia.settings import MARKDOWN_EXTENSIONS
+from zinnia.settings import ENTRY_DETAIL_TEMPLATES
+from zinnia.settings import ENTRY_CONTENT_TEMPLATES
+from zinnia.settings import AUTO_CLOSE_COMMENTS_AFTER
+from zinnia.settings import AUTO_CLOSE_PINGBACKS_AFTER
+from zinnia.settings import AUTO_CLOSE_TRACKBACKS_AFTER
+from zinnia.managers import entries_published
+from zinnia.managers import EntryPublishedManager
+from zinnia.managers import DRAFT, HIDDEN, PUBLISHED
+from zinnia.url_shortener import get_url_shortener
+
+
+class CoreEntry(models.Model):
+ """
+ Abstract core entry model class providing
+ the fields and methods required for publishing
+ content over time.
+ """
+ STATUS_CHOICES = ((DRAFT, _('draft')),
+ (HIDDEN, _('hidden')),
+ (PUBLISHED, _('published')))
+
+ title = models.CharField(
+ _('title'), max_length=255)
+
+ slug = models.SlugField(
+ _('slug'), max_length=255,
+ unique_for_date='creation_date',
+ help_text=_("Used to build the entry's URL."))
+
+ status = models.IntegerField(
+ _('status'), choices=STATUS_CHOICES, default=DRAFT)
+
+ start_publication = models.DateTimeField(
+ _('start publication'), blank=True, null=True,
+ help_text=_('Start date of publication.'))
+
+ end_publication = models.DateTimeField(
+ _('end publication'), blank=True, null=True,
+ help_text=_('End date of publication.'))
+
+ sites = models.ManyToManyField(
+ Site,
+ related_name='entries',
+ verbose_name=_('sites'),
+ help_text=_('Sites where the entry will be published.'))
+
+ creation_date = models.DateTimeField(
+ _('creation date'), default=timezone.now,
+ help_text=_("Used to build the entry's URL."))
+
+ last_update = models.DateTimeField(
+ _('last update'), default=timezone.now)
+
+ objects = models.Manager()
+ published = EntryPublishedManager()
+
+ @property
+ def is_actual(self):
+ """
+ Checks if an entry is within his publication period.
+ """
+ now = timezone.now()
+ if self.start_publication and now < self.start_publication:
+ return False
+
+ if self.end_publication and now >= self.end_publication:
+ return False
+ return True
+
+ @property
+ def is_visible(self):
+ """
+ Checks if an entry is visible and published.
+ """
+ return self.is_actual and self.status == PUBLISHED
+
+ @cached_property
+ def previous_entry(self):
+ """
+ Returns the previous published entry if exists.
+ """
+ entries = self.__class__.published.filter(
+ creation_date__lt=self.creation_date)[:1]
+ if entries:
+ return entries[0]
+
+ @cached_property
+ def next_entry(self):
+ """
+ Returns the next published entry if exists.
+ """
+ entries = self.__class__.published.filter(
+ creation_date__gt=self.creation_date).order_by('creation_date')[:1]
+ if entries:
+ return entries[0]
+
+ @property
+ def short_url(self):
+ """
+ Returns the entry's short url.
+ """
+ return get_url_shortener()(self)
+
+ @models.permalink
+ def get_absolute_url(self):
+ """
+ Builds and returns the entry's URL based on
+ the slug and the creation date.
+ """
+ creation_date = timezone.localtime(self.creation_date)
+ return ('zinnia_entry_detail', (), {
+ 'year': creation_date.strftime('%Y'),
+ 'month': creation_date.strftime('%m'),
+ 'day': creation_date.strftime('%d'),
+ 'slug': self.slug})
+
+ def __unicode__(self):
+ return u'%s: %s' % (self.title, self.get_status_display())
+
+ class Meta:
+ """
+ CoreEntry's meta informations.
+ """
+ abstract = True
+ app_label = 'zinnia'
+ ordering = ['-creation_date']
+ get_latest_by = 'creation_date'
+ verbose_name = _('entry')
+ verbose_name_plural = _('entries')
+ index_together = [['slug', 'creation_date']]
+ permissions = (('can_view_all', 'Can view all entries'),
+ ('can_change_status', 'Can change status'),
+ ('can_change_author', 'Can change author(s)'), )
+
+
+class ContentEntry(models.Model):
+ """
+ Abstract content model class providing field
+ and methods to write content inside an entry.
+ """
+ content = models.TextField(_('content'), blank=True)
+
+ @property
+ def html_content(self):
+ """
+ Returns the "content" field formatted in HTML.
+ """
+ if MARKUP_LANGUAGE == 'markdown':
+ return markdown(self.content, MARKDOWN_EXTENSIONS)
+ elif MARKUP_LANGUAGE == 'textile':
+ return textile(self.content)
+ elif MARKUP_LANGUAGE == 'restructuredtext':
+ return restructuredtext(self.content)
+ elif not '</p>' in self.content:
+ return linebreaks(self.content)
+ return self.content
+
+ @property
+ def word_count(self):
+ """
+ Counts the number of words used in the content.
+ """
+ return len(strip_tags(self.html_content).split())
+
+ class Meta:
+ abstract = True
+
+
+class DiscussionsEntry(models.Model):
+ """
+ Abstract discussion model class providing
+ the fields and methods to manage the discussions
+ (comments, pingbacks, trackbacks).
+ """
+ comment_enabled = models.BooleanField(
+ _('comments enabled'), default=True,
+ help_text=_('Allows comments if checked.'))
+ pingback_enabled = models.BooleanField(
+ _('pingbacks enabled'), default=True,
+ help_text=_('Allows pingbacks if checked.'))
+ trackback_enabled = models.BooleanField(
+ _('trackbacks enabled'), default=True,
+ help_text=_('Allows trackbacks if checked.'))
+
+ comment_count = models.IntegerField(
+ _('comment count'), default=0)
+ pingback_count = models.IntegerField(
+ _('pingback count'), default=0)
+ trackback_count = models.IntegerField(
+ _('trackback count'), default=0)
+
+ @property
+ def discussions(self):
+ """
+ Returns a queryset of the published discussions.
+ """
+ return comments.get_model().objects.for_model(
+ self).filter(is_public=True, is_removed=False)
+
+ @property
+ def comments(self):
+ """
+ Returns a queryset of the published comments.
+ """
+ return self.discussions.filter(Q(flags=None) | Q(
+ flags__flag=CommentFlag.MODERATOR_APPROVAL))
+
+ @property
+ def pingbacks(self):
+ """
+ Returns a queryset of the published pingbacks.
+ """
+ return self.discussions.filter(flags__flag=PINGBACK)
+
+ @property
+ def trackbacks(self):
+ """
+ Return a queryset of the published trackbacks.
+ """
+ return self.discussions.filter(flags__flag=TRACKBACK)
+
+ def discussion_is_still_open(self, discussion_type, auto_close_after):
+ """
+ Checks if a type of discussion is still open
+ are a certain number of days.
+ """
+ discussion_enabled = getattr(self, discussion_type)
+ if (discussion_enabled and isinstance(auto_close_after, int)
+ and auto_close_after >= 0):
+ return (timezone.now() - (
+ self.start_publication or self.creation_date)).days < \
+ auto_close_after
+ return discussion_enabled
+
+ @property
+ def comments_are_open(self):
+ """
+ Checks if the comments are open with the
+ AUTO_CLOSE_COMMENTS_AFTER setting.
+ """
+ return self.discussion_is_still_open(
+ 'comment_enabled', AUTO_CLOSE_COMMENTS_AFTER)
+
+ @property
+ def pingbacks_are_open(self):
+ """
+ Checks if the pingbacks are open with the
+ AUTO_CLOSE_PINGBACKS_AFTER setting.
+ """
+ return self.discussion_is_still_open(
+ 'pingback_enabled', AUTO_CLOSE_PINGBACKS_AFTER)
+
+ @property
+ def trackbacks_are_open(self):
+ """
+ Checks if the trackbacks are open with the
+ AUTO_CLOSE_TRACKBACKS_AFTER setting.
+ """
+ return self.discussion_is_still_open(
+ 'trackback_enabled', AUTO_CLOSE_TRACKBACKS_AFTER)
+
+ class Meta:
+ abstract = True
+
+
+class RelatedEntry(models.Model):
+ """
+ Abstract model class for making manual relations
+ between the differents entries.
+ """
+ related = models.ManyToManyField(
+ 'self',
+ blank=True, null=True,
+ verbose_name=_('related entries'))
+
+ @property
+ def related_published(self):
+ """
+ Returns only related entries published.
+ """
+ return entries_published(self.related)
+
+ class Meta:
+ abstract = True
+
+
+class ExcerptEntry(models.Model):
+ """
+ Abstract model class to add an excerpt to the entries.
+ """
+ excerpt = models.TextField(
+ _('excerpt'), blank=True,
+ help_text=_('Optional element.'))
+
+ class Meta:
+ abstract = True
+
+
+class ImageEntry(models.Model):
+ """
+ Abstract model class to add an image to the entries.
+ """
+ image = models.ImageField(
+ _('image'), blank=True, upload_to=UPLOAD_TO,
+ help_text=_('Used for illustration.'))
+
+ class Meta:
+ abstract = True
+
+
+class FeaturedEntry(models.Model):
+ """
+ Abstract model class to mark entries as featured.
+ """
+ featured = models.BooleanField(
+ _('featured'), default=False)
+
+ class Meta:
+ abstract = True
+
+
+class AuthorsEntry(models.Model):
+ """
+ Abstract model class to add relationship
+ between the entries and their authors.
+ """
+ authors = models.ManyToManyField(
+ 'zinnia.Author',
+ related_name='entries',
+ blank=True, null=False,
+ verbose_name=_('authors'))
+
+ class Meta:
+ abstract = True
+
+
+class CategoriesEntry(models.Model):
+ """
+ Abstract model class to categorize the entries.
+ """
+ categories = models.ManyToManyField(
+ 'zinnia.Category',
+ related_name='entries',
+ blank=True, null=True,
+ verbose_name=_('categories'))
+
+ class Meta:
+ abstract = True
+
+
+class TagsEntry(models.Model):
+ """
+ Abstract lodel class to add tags to the entries.
+ """
+ tags = TagField(_('tags'))
+
+ @property
+ def tags_list(self):
+ """
+ Return iterable list of tags.
+ """
+ return parse_tag_input(self.tags)
+
+ class Meta:
+ abstract = True
+
+
+class LoginRequiredEntry(models.Model):
+ """
+ Abstract model class to restrcit the display
+ of the entry on authenticated users.
+ """
+ login_required = models.BooleanField(
+ _('login required'), default=False,
+ help_text=_('Only authenticated users can view the entry.'))
+
+ class Meta:
+ abstract = True
+
+
+class PasswordRequiredEntry(models.Model):
+ """
+ Abstract model class to restrict the display
+ of the entry to users knowing the password.
+ """
+ password = models.CharField(
+ _('password'), max_length=50, blank=True,
+ help_text=_('Protects the entry with a password.'))
+
+ class Meta:
+ abstract = True
+
+
+class ContentTemplateEntry(models.Model):
+ """
+ Abstract model class to display entry's content
+ with a custom template.
+ """
+ content_template = models.CharField(
+ _('content template'), max_length=250,
+ default='zinnia/_entry_detail.html',
+ choices=[('zinnia/_entry_detail.html', _('Default template'))] +
+ ENTRY_CONTENT_TEMPLATES,
+ help_text=_("Template used to display the entry's content."))
+
+ class Meta:
+ abstract = True
+
+
+class DetailTemplateEntry(models.Model):
+ """
+ Abstract model class to display entries with a
+ custom template if needed on the detail page.
+ """
+ detail_template = models.CharField(
+ _('detail template'), max_length=250,
+ default='entry_detail.html',
+ choices=[('entry_detail.html', _('Default template'))] +
+ ENTRY_DETAIL_TEMPLATES,
+ help_text=_("Template used to display the entry's detail page."))
+
+ class Meta:
+ abstract = True
+
+
+class AbstractEntry(
+ CoreEntry,
+ ContentEntry,
+ DiscussionsEntry,
+ RelatedEntry,
+ ExcerptEntry,
+ ImageEntry,
+ FeaturedEntry,
+ AuthorsEntry,
+ CategoriesEntry,
+ TagsEntry,
+ LoginRequiredEntry,
+ PasswordRequiredEntry,
+ ContentTemplateEntry,
+ DetailTemplateEntry):
+ """
+ Final abstract entry model class assembling
+ all the abstract entry model classes into a single one.
+
+ In this manner we can override some fields without
+ reimplemting all the AbstractEntry.
+ """
+
+ class Meta(CoreEntry.Meta):
+ abstract = True
View
35 zinnia/tests/entry.py
@@ -12,14 +12,15 @@
from django.contrib.comments.models import CommentFlag
from django.contrib.auth.tests.utils import skipIfCustomUser
-from zinnia.models import entry
+from zinnia.managers import PUBLISHED
+from zinnia.models_bases import entry
from zinnia.models.entry import Entry
from zinnia.models.author import Author
-from zinnia.managers import PUBLISHED
from zinnia.flags import PINGBACK, TRACKBACK
-from zinnia.models.entry import get_entry_base_model
-from zinnia.models.entry import EntryAbstractClass
from zinnia.tests.utils import datetime
+from zinnia.models import entry as entry_models
+from zinnia.models_bases.entry import AbstractEntry
+from zinnia.models.entry import get_entry_base_model
from zinnia import url_shortener as shortener_settings
@@ -307,23 +308,19 @@ def test_html_content_restructuredtext(self):
class EntryGetBaseModelTestCase(TestCase):
def setUp(self):
- self.original_entry_base_model = entry.ENTRY_BASE_MODEL
+ self.original_entry_base_model = entry_models.ENTRY_BASE_MODEL
def tearDown(self):
- entry.ENTRY_BASE_MODEL = self.original_entry_base_model
+ entry_models.ENTRY_BASE_MODEL = self.original_entry_base_model
def test_get_entry_base_model(self):
- entry.ENTRY_BASE_MODEL = ''
- self.assertEquals(get_entry_base_model(), EntryAbstractClass)
+ entry_models.ENTRY_BASE_MODEL = ''
+ self.assertEquals(get_entry_base_model(), AbstractEntry)
- entry.ENTRY_BASE_MODEL = 'mymodule.myclass'
- try:
- with warnings.catch_warnings(record=True) as w:
- self.assertEquals(get_entry_base_model(), EntryAbstractClass)
- self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
- except AttributeError:
- # Fail under Python2.5, because of'warnings.catch_warnings'
- pass
-
- entry.ENTRY_BASE_MODEL = 'zinnia.models.entry.EntryAbstractClass'
- self.assertEquals(get_entry_base_model(), EntryAbstractClass)
+ entry_models.ENTRY_BASE_MODEL = 'mymodule.myclass'
+ with warnings.catch_warnings(record=True) as w:
+ self.assertEquals(get_entry_base_model(), AbstractEntry)
+ self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
+
+ entry_models.ENTRY_BASE_MODEL = 'zinnia.models.entry.AbstractEntry'
+ self.assertEquals(get_entry_base_model(), AbstractEntry)
View
34 zinnia/tests/spam_checker.py
@@ -11,28 +11,20 @@ class SpamCheckerTestCase(TestCase):
"""Test cases for zinnia.spam_checker"""
def test_get_spam_checker(self):
- try:
- with warnings.catch_warnings(record=True) as w:
- self.assertEquals(get_spam_checker('mymodule.myclass'), None)
- self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
- self.assertEquals(
- str(w[-1].message),
- 'mymodule.myclass backend cannot be imported')
- except AttributeError:
- # Fail under Python2.5, because of'warnings.catch_warnings'
- pass
+ with warnings.catch_warnings(record=True) as w:
+ self.assertEquals(get_spam_checker('mymodule.myclass'), None)
+ self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
+ self.assertEquals(
+ str(w[-1].message),
+ 'mymodule.myclass backend cannot be imported')
- try:
- with warnings.catch_warnings(record=True) as w:
- self.assertEquals(
- get_spam_checker('zinnia.tests.custom_spam_checker'), None)
- self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
- self.assertEquals(
- str(w[-1].message),
- 'This backend only exists for testing')
- except AttributeError:
- # Fail under Python2.5, because of'warnings.catch_warnings'
- pass
+ with warnings.catch_warnings(record=True) as w:
+ self.assertEquals(
+ get_spam_checker('zinnia.tests.custom_spam_checker'), None)
+ self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
+ self.assertEquals(
+ str(w[-1].message),
+ 'This backend only exists for testing')
self.assertEquals(
get_spam_checker('zinnia.spam_checker.backends.all_is_spam'),
View
32 zinnia/tests/url_shortener.py
@@ -19,28 +19,20 @@ def tearDown(self):
def test_get_url_shortener(self):
us_settings.URL_SHORTENER_BACKEND = 'mymodule.myclass'
- try:
- with warnings.catch_warnings(record=True) as w:
- self.assertEquals(get_url_shortener(), default_backend)
- self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
- self.assertEquals(
- str(w[-1].message),
- 'mymodule.myclass backend cannot be imported')
- except AttributeError:
- # Fail under Python2.5, because of'warnings.catch_warnings'
- pass
+ with warnings.catch_warnings(record=True) as w:
+ self.assertEquals(get_url_shortener(), default_backend)
+ self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
+ self.assertEquals(
+ str(w[-1].message),
+ 'mymodule.myclass backend cannot be imported')
us_settings.URL_SHORTENER_BACKEND = 'zinnia.tests.custom_url_shortener'
- try:
- with warnings.catch_warnings(record=True) as w:
- self.assertEquals(get_url_shortener(), default_backend)
- self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
- self.assertEquals(
- str(w[-1].message),
- 'This backend only exists for testing')
- except AttributeError:
- # Fail under Python2.5, because of'warnings.catch_warnings'
- pass
+ with warnings.catch_warnings(record=True) as w:
+ self.assertEquals(get_url_shortener(), default_backend)
+ self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
+ self.assertEquals(
+ str(w[-1].message),
+ 'This backend only exists for testing')
us_settings.URL_SHORTENER_BACKEND = 'zinnia.url_shortener'\
'.backends.default'

No commit comments for this range

Something went wrong with that request. Please try again.