Skip to content
Snippets Groups Projects
models.py 9.66 KiB
Newer Older
Olivier Le Brouster's avatar
Olivier Le Brouster committed
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sys
from datetime import datetime

from django.core.urlresolvers import reverse
from django.db import models
from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import python_2_unicode_compatible
from django.utils import six

newsbox_models = []

def newsbox_mcls_processor(mcls, class_name, class_bases, class_attrs, base, newsbox_opts):
    class_attrs['newsbox_title'] = models.CharField(
        verbose_name=_('title'),
        max_length=255)
    class_attrs['newsbox_slug'] = models.SlugField(
        verbose_name=_('slug'),
        unique_for_date='newsbox_date',
        help_text=_('The part of the title that is used in the URL'))
    class_attrs['newsbox_summary'] = models.TextField(
        verbose_name=_('summary'))
    class_attrs['newsbox_body'] = models.TextField(
        verbose_name=_('body'),
        null=False, blank=True)
    newsbox_opts['trans_fieldnames']+=[
        'newsbox_title', 'newsbox_slug',
        'newsbox_summary', 'newsbox_body']

def newsboxseo_mcls_processor(mcls, class_name, class_bases, class_attrs, base, newsbox_opts):
    class_attrs['newsbox_meta_description'] = models.TextField(
        verbose_name=_('meta description'),
        max_length=255, blank=True, null=True)
    class_attrs['newsbox_meta_keywords'] = models.CharField(
        verbose_name=_('meta keywords'),
        max_length=255, blank=True, null=True)

    newsbox_opts['trans_fieldnames']+=[
        'newsbox_meta_description', 'newsbox_meta_keywords']

class NewsboxManager(models.Manager):
    """
    Filter published news
    """

    def published(self):
        now = datetime.utcnow().replace(tzinfo=utc)
        query_set = super(NewsboxManager, self).get_query_set().filter(
            # Only published News
            models.Q(newsbox_published=True),
        ).exclude(
            # exclude news with publication start date in the future
            models.Q(newsbox_publication_start_date__gt=now)
        )
        
        if issubclass(self.model, NewsboxExpiredBase):
            query_set = query_set.exclude(
                # exclude news which are obsolete
                models.Q(newsbox_publication_end_date__lte=now)
            )
        return query_set


class NewsboxModelBase(models.base.ModelBase):
    """
    Metaclass used to inject django-newsboxfunctionalities into django
    orm.
    """

    def __new__(mcls, class_name, class_bases, class_attrs):
        super_new = super(NewsboxModelBase, mcls).__new__

        # six.with_metaclass() inserts an extra class called 'NewBase' in the
        # inheritance tree: Model -> NewBase -> object. But the initialization
        # should be executed only once for a given model class.

        # attrs will never be empty for classes declared in the standard way
        # (ie. with the `class` keyword). This is quite robust.
        if class_name == 'NewBase' and class_attrs == {}:
            return super_new(mcls, class_name, class_bases, class_attrs)

        # Also ensure initialization is only performed for subclasses of Model
        # (excluding Model class itself).
        parents = [b for b in class_bases if isinstance(b, NewsboxModelBase) and
                not (b.__name__ == 'NewBase' and b.__mro__ == (b, object))]
        if not parents:
            return super_new(mcls, class_name, class_bases, class_attrs)

        #retrieve newsbox options in Meta class and delete these from the 
        #Meta class to avoid django complain about unsupported keys.
        #We register our meta options in `_newsbox_meta`
        attr_meta = class_attrs.get('Meta', None)
        if attr_meta and not getattr(attr_meta, 'ordering', None):
            class_attrs['Meta'].ordering = ('-newsbox_date',)
        abstract = getattr(attr_meta, 'abstract', False)
        newsbox_opts = {}
        newsbox_opts_keys = [
            'detail_url_name', 
            'metaclass_base_processor', 
            'metaclass_final_processor', 
            'trans_fieldnames']
        for opt in newsbox_opts_keys:
            newsbox_opt = 'newsbox_' + opt
            if hasattr(attr_meta, newsbox_opt):
                newsbox_opts[opt] = getattr(attr_meta, newsbox_opt)
                delattr(attr_meta, newsbox_opt)

        if 'trans_fieldnames' not in newsbox_opts:
            newsbox_opts['trans_fieldnames'] = []

        # register newsbox_objects manager
        class_attrs['newsbox_objects'] = NewsboxManager()
        
        #Call specific metaclass processor of all bases
        if not abstract :
            for base in class_bases:
                try:
                    processor = base._newsbox_meta['metaclass_base_processor']
                except (AttributeError, KeyError):
                    continue
                processor = getattr(sys.modules[base.__module__], processor)
                processor(mcls, class_name, class_bases, class_attrs, base, newsbox_opts)

            for base in class_bases:
                try:
                    processor = base._newsbox_meta['metaclass_final_processor']
                except (AttributeError, KeyError):
                    continue
                processor = getattr(sys.modules[base.__module__], processor)
                processor(mcls, class_name, class_bases, class_attrs, base, newsbox_opts)

        if not 'default_manager' in class_attrs:
            class_attrs['default_manager'] = class_attrs['newsbox_objects']
        if not 'objects' in class_attrs:
            class_attrs['objects'] = class_attrs['newsbox_objects']

        # contructs class
        cls = super_new(mcls, class_name, class_bases, class_attrs)

        #Add the detail_url_name to _newsbox_meta. if not defined, we build the
        #default one with this format : <app_label>_<model_name>_detail
        #Names ar lowered.
        if not abstract:
            detail_url_name = newsbox_opts.get(
                'detail_url_name',
                "%s_%s_detail" % (cls._meta.app_label.lower(),
                    cls._meta.model_name.lower(),)
            )

            newsbox_opts.update(
                detail_url_name=detail_url_name,
            )
            newsbox_models.append(cls)
        cls.add_to_class('_newsbox_meta', newsbox_opts)
        return cls


@python_2_unicode_compatible
class NewsboxBase(six.with_metaclass(NewsboxModelBase, models.Model)):
    """
    Abstract class to build your own news
    """

    # Automatic fields
    newsbox_creation_date = models.DateTimeField(
        verbose_name=_('creation date'),
        auto_now_add=True,
        editable=False)
    newsbox_changed_date = models.DateTimeField(
        verbose_name=_('last update date'),
        auto_now=True,
        editable=False)

    # Basic Publication Fields
    newsbox_date = models.DateTimeField(
        verbose_name=_('date'),
        default=datetime.now, db_index=True,
    )

    newsbox_publication_start_date = models.DateTimeField(
        verbose_name=_('publication start date'),
        default=datetime.now, db_index=True,
        help_text=_(
            'When the news should go live. '
            'Status must be "Published" for news to go live.'))

    newsbox_published = models.BooleanField(
        verbose_name=_('published'),
        blank=True,
        default=True,)

    default_manager = NewsboxManager()
    objects = default_manager

    def __str__(self):
        return six.text_type(self.newsbox_title)

    def has_publish_permission(self, request):
        if request.user.is_superuser:
            return True
        return request.user.has_perm('newsbox.publish_news', self)

    def has_change_permission(self, request):
        if request.user.is_superuser:
            return True
        return request.user.has_perm('newsbox.change_news', self)

    def is_published(self):
        if not self.newsbox_published:
            return False

        if isinstance(self, NewsboxExpiredBase):
            now = datetime.utcnow().replace(tzinfo=utc)
            if self.newsbox_publication_start_date and now < self.newsbox_publication_start_date:
Olivier Le Brouster's avatar
Olivier Le Brouster committed
                return False
            if self.newsbox_publication_end_date and now > self.newsbox_publication_end_date:
Olivier Le Brouster's avatar
Olivier Le Brouster committed
                return False

        return True

    def get_slug(self, *args, **kwargs):
        return self.newsbox_slug

    def get_absolute_url(self, *args, **kwargs):
        return reverse(self._newsbox_meta['detail_url_name'], args=(
            self.newsbox_date.strftime("%Y"),
            self.newsbox_date.strftime("%m"),
            self.newsbox_date.strftime("%d"),
            self.get_slug(),))

    class Meta:
        abstract = True
        verbose_name = _('news')
        verbose_name_plural = _('news')
        ordering = ('-newsbox_date',)
        newsbox_metaclass_base_processor = 'newsbox_mcls_processor'

class NewsboxExpiredBase(six.with_metaclass(NewsboxModelBase, models.Model)):
    """
    Define News which expires in time and will not be displayed in front
    """
    newsbox_publication_end_date = models.DateTimeField(
        verbose_name=_('publication end date'),
        null=True, blank=True, db_index=True,
        help_text=_(
            'When to expire the news.'
            'Leave empty to never expire.'))

    class Meta:
        abstract = True

class NewsboxSEOBase(six.with_metaclass(NewsboxModelBase, models.Model)):
    """
    Define News which have SEO fields
    """

    # SEO Fields
    newsbox_indexed = models.BooleanField(
        verbose_name=_('indexed'),
        default=True,
        help_text=_(
            'An unindexed news will not be indexed by search engines.'))

    class Meta:
        abstract = True
        newsbox_metaclass_base_processor = 'newsboxseo_mcls_processor'