# -*- 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: return False if self.newsbox_publication_end_date and now > self.newsbox_publication_end_date: 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'