# -*- coding: utf-8 -*- # Future imports from __future__ import unicode_literals # Standard libs import copy import itertools # Django imports from django.conf.urls import url from django.contrib import admin from django.contrib import messages from django.contrib.admin.models import CHANGE from django.contrib.admin.models import LogEntry from django.contrib.contenttypes.models import ContentType from django.templatetags.static import static try: # Django imports from django.urls import reverse except ImportError: from django.core.urlresolvers import reverse # Django imports from django.http import HttpResponseForbidden from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.utils import formats from django.utils.html import format_html from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext_lazy # Third Party import six def get_fieldset_by_field(name, fieldsets): """ Return fieldset containing a field given by name """ for fieldset in fieldsets: options = fieldset[1] if name in options.get('fields', {}): return fieldset return None def remove_field_from_fieldsets(name, fieldsets, remove_empty_fieldset=True): """ Remove a field from all fieldset in fieldsets. """ while True: fieldset = get_fieldset_by_field(name, fieldsets) if fieldset: fieldset[1]['fields'].remove(name) if remove_empty_fieldset and len(fieldset[1]['fields']) == 0: fieldsets.remove(fieldset) else: break def add_fields_to_fieldset( fields, fieldsets, after_field=None, same_fieldset_as=None, default_fieldset_name=None, default_fieldset_classes=[], default_fieldset_position=None, replace_existing_field=False, remove_empty_fieldset=False, ): """ Add fields in a fieldsets attribute Try to find a fieldset containing same_fieldset_as value in fields. If not found a new fieldset is created with name `default_fieldset_name`, position `default_fieldset_position` and classes `default_fieldset_classes`. Fields are inserted just after after_field. If not found, they are inserted at the end. If replace_existing_field is True, existing fields in fieldsets with same name are removed. """ if same_fieldset_as is None and after_field: same_fieldset_as = after_field # Find fieldset fieldset = get_fieldset_by_field(same_fieldset_as, fieldsets) if fieldset is None: fieldset = (default_fieldset_name, { 'fields': [], 'classes': default_fieldset_classes, }) allready_inserted = False else: allready_inserted = True # Insert fields index = None if after_field: index = fieldset[1]['fields'].index(after_field) + 1 index = index or len(fieldset[1]['fields']) for field in fields: if replace_existing_field: remove_field_from_fieldsets( field, fieldsets, remove_empty_fieldset=remove_empty_fieldset) elif get_fieldset_by_field(field, fieldsets) is not None: continue fieldset[1]['fields'].insert(index, field) index += 1 # Insert fieldset if needed. if not allready_inserted and len(fieldset[1]['fields']) > 0: if default_fieldset_position is not None: fieldsets.insert(default_fieldset_position, fieldset) else: fieldsets.append(fieldset) class NewsboxBaseAdmin(admin.ModelAdmin): list_filter = ('newsbox_publication_start_date', 'newsbox_status', ) list_display = ('get_newsbox_title',) list_display_links = ('get_newsbox_title',) fieldsets = [ (None, { 'fields': ['newsbox_title', 'newsbox_slug', 'newsbox_date', 'newsbox_status', ], }), (_('Content'), { 'fields': ['newsbox_summary', 'newsbox_body'], }), ] actions = ['publish', 'unpublish'] save_as = True def get_list_display(self, request): if getattr(self.__class__, 'list_display', None) == admin.ModelAdmin.list_display: self.list_display = ['get_newsbox_title', 'newsbox_date_short', 'changestatus_link'] return super(NewsboxBaseAdmin, self).get_list_display(request) def get_prepopulated_fields(self, request, object=None): pfields = super(NewsboxBaseAdmin, self).get_prepopulated_fields(request, object) or {} pfields.update({'newsbox_slug': ('newsbox_title',)}) return pfields def changestatus_link(self, obj): if obj.newsbox_status == obj.STATUS_PUBLISHED: title = _('Unpublish this %s') % six.text_type( obj._meta.verbose_name) icon_url = static('admin/img/icon-yes.gif') else: title = _('Publish this %s') % six.text_type( obj._meta.verbose_name) icon_url = static('admin/img/icon-no.gif') url_name = '%s_%s_change_status' % (self.model._meta.model_name, self.model._meta.app_label) return format_html( '<a href="{}" title="{}"><img src="{}" alt="{}" /></a>', reverse('admin:%s' % url_name, args=[obj.pk, ]), title, icon_url, obj.get_newsbox_status_display() ) changestatus_link.admin_order_field = 'newsbox_status' changestatus_link.short_description = _('published') def newsbox_date_short(self, obj): return format_html('<span title="{}">{}</span>', formats.date_format(obj.newsbox_publication_start_date, 'DATETIME_FORMAT'), formats.date_format(obj.newsbox_publication_start_date, 'SHORT_DATE_FORMAT') ) newsbox_date_short.short_description = _('pub. date') def get_fieldsets(self, request, obj=None): """ Prevent anoying modification of fieldsets class attribute. """ fieldsets = super(NewsboxBaseAdmin, self).get_fieldsets(request, obj) return copy.deepcopy(fieldsets) def get_newsbox_title(self, obj): return obj.newsbox_title get_newsbox_title.short_description = _('Title') def get_urls(self): url_prefix = '%s_%s_' % (self.model._meta.model_name, self.model._meta.app_label) return [ url(r'^([0-9]+)/change-status/$', self.admin_site.admin_view(self.change_status), name='%schange_status' % url_prefix,), url(r'^([0-9]+)/publish/$', self.admin_site.admin_view(self.publish_one), name='%spublish_one' % url_prefix,), url(r'^([0-9]+)/unpublish/$', self.admin_site.admin_view(self.unpublish_one), name='%sunpublish_one' % url_prefix,) ] + super(NewsboxBaseAdmin, self).get_urls() def change_status(self, request, news_id): """ Switch the status of a news """ obj = get_object_or_404(self.model, pk=news_id) if not obj.has_publish_permission(request.user): return HttpResponseForbidden( _('You do not have permission to publish this %s') % (six.text_type(self.model._meta.verbose_name),)) old_status = obj.newsbox_status if obj.newsbox_status == obj.STATUS_PUBLISHED: obj.newsbox_status = obj.STATUS_ARCHIVED else: obj.newsbox_status = obj.STATUS_PUBLISHED obj.save() log_msg = _('publication status changed from %(old)s to %(new)s') % { 'old': old_status, 'new': obj.newsbox_status, } if obj.newsbox_status == obj.STATUS_PUBLISHED: msg = _('The %(objtype)s "%(objtitle)s" has been successfully published') else: msg = _('The %(objtype)s "%(objtitle)s" has been successfully unpublished') messages.info(request, msg % {'objtype': six.text_type(self.model._meta.verbose_name), 'objtitle': six.text_type(obj)}) LogEntry.objects.log_action( user_id=request.user.id, content_type_id=ContentType.objects.get_for_model(self.model).pk, object_id=news_id, object_repr=six.text_type(obj), action_flag=CHANGE, change_message=log_msg, ) return HttpResponseRedirect(request.GET.get('next', '../../')) def publish_one(self, request, news_id): obj = get_object_or_404(self.model, pk=news_id) if not obj.has_publish_permission(request.user): return HttpResponseForbidden( _('You do not have permission to publish this %s') % (six.text_type(self.model._meta.verbose_name),)) self.publish(request, self.model.objects.filter(pk=news_id)) return HttpResponseRedirect(request.GET.get('next', '../../')) def unpublish_one(self, request, news_id): obj = get_object_or_404(self.model, pk=news_id) if not obj.has_publish_permission(request.user): return HttpResponseForbidden( _('You do not have permission to publish this %s') % (six.text_type(self.model._meta.verbose_name),)) self.unpublish(request, self.model.objects.filter(pk=news_id)) return HttpResponseRedirect(request.GET.get('next', '../../')) def publish(self, request, queryset): """ Marks selected news items as published """ ok = 0 content_type_id = ContentType.objects.get_for_model(self.model).pk for obj in queryset: if obj.is_published: continue if not obj.has_publish_permission(request.user): messages.error( request, _( 'You do not have permission to publish the ' '%(objtype)s "%(objtitle)s"' ) % { 'objtype': six.text_type(self.model._meta.verbose_name), 'objtitle': six.text_type(obj) } ) continue obj.publish() LogEntry.objects.log_action(user_id=request.user.id, content_type_id=content_type_id, object_id=obj.pk, object_repr=six.text_type(obj), action_flag=CHANGE, change_message=_('Published'),) ok += 1 last_obj_ok = obj if ok == 1: messages.success(request, _('The %(objtype)s "%(objtitle)s" has been published') % { 'objtype': self.model._meta.verbose_name, 'objtitle': last_obj_ok, }) else: messages.success(request, ungettext_lazy( '%(nb)d %(objtype)s has been published', '%(nb)d %(objtype)s have been published', ok) % {'nb': ok, 'objtype': self.model._meta.verbose_name_plural,}) publish.short_description = _('Publish selected %(verbose_name_plural)s') def unpublish(self, request, queryset): """ Marks selected news items as unpublished """ ok = 0 content_type_id = ContentType.objects.get_for_model(self.model).pk for obj in queryset: if not obj.is_published: continue if not obj.has_publish_permission(request.user): messages.error( request, _( 'You do not have permission to unpublish the ' '%(objtype)s "%(objtitle)s"' ) % { 'objtype': six.text_type(self.model._meta.verbose_name), 'objtitle': six.text_type(obj) } ) continue obj.unpublish() LogEntry.objects.log_action(user_id=request.user.id, content_type_id=content_type_id, object_id=obj.pk, object_repr=six.text_type(obj), action_flag=CHANGE, change_message=_('Unpublished'),) ok += 1 last_obj_ok = obj if ok == 1: messages.success(request, _('The %(objtype)s "%(objtitle)s" has been unpublished') % { 'objtype': self.model._meta.verbose_name, 'objtitle': last_obj_ok, }) else: messages.success(request, ungettext_lazy( '%(nb)d %(objtype)s has been unpublished', '%(nb)d %(objtype)s have been unpublished', ok) % {'nb': ok, 'objtype': self.model._meta.verbose_name_plural}) unpublish.short_description = _('Archive selected %(verbose_name_plural)s') def ensure_slug_uniq_queryset(self, request, obj, slug): y, m, d = (obj.newsbox_publication_start_date.year, obj.newsbox_publication_start_date.month, obj.newsbox_publication_start_date.day) queryset = type(obj).objects.filter(newsbox_publication_start_date__year=y, newsbox_publication_start_date__month=m, newsbox_publication_start_date__day=d, newsbox_slug=slug) if obj.pk: queryset = queryset.exclude(pk=obj.pk) return queryset def ensure_slug_uniq(self, request, obj): max_length = 50 slug = obj.newsbox_slug for x in itertools.count(1): if not self.ensure_slug_uniq_queryset(request, obj, slug).count(): break suffix = "-%d" % x slug = "%s%s" % (obj.newsbox_slug[:max_length-len(suffix)], suffix) obj.newsbox_slug = slug def save_model(self, request, obj, form, change): self.ensure_slug_uniq(request, obj) return super(NewsboxBaseAdmin, self).save_model(request, obj, form, change) class NewsboxAdmin(NewsboxBaseAdmin): pass class NewsboxExpiredAdmin(NewsboxBaseAdmin): def newsbox_publication_dates_short(self, obj): output = [] title = [] css_class = [] title.append(formats.date_format(obj.newsbox_publication_start_date, 'DATETIME_FORMAT')) output.append('<span class="from">%s</span>' % formats.date_format(obj.newsbox_publication_start_date, 'SHORT_DATE_FORMAT')) css_class.append('from') if not obj.newsbox_publication_end_date: title.append(_('never')) else: title.append( formats.date_format(obj.newsbox_publication_end_date, 'DATETIME_FORMAT')) output.append('<span class="to">%s</span>' % formats.date_format(obj.newsbox_publication_end_date, 'SHORT_DATE_FORMAT')) css_class.append('to') css_class = '_'.join(css_class) title = _('from %(from)s to %(to)s') % {'from': title[0], 'to': title[1]} output = ('<span> %s </span>' % _('to')).join(output) return format_html('<abbr class="newsbox_pub_dates %s" title="%s">%s</abbr>' % ( css_class, title, output)) newsbox_publication_dates_short.short_description = _('publication') def get_fieldsets(self, request, obj=None): fieldsets = super(NewsboxExpiredAdmin, self).get_fieldsets(request, obj) for fieldset in fieldsets: if 'newsbox_publication_end_date' in fieldset[1]['fields']: return fieldsets remove_field_from_fieldsets('newsbox_status', fieldsets) add_fields_to_fieldset( ['newsbox_status', 'newsbox_publication_start_date', 'newsbox_publication_end_date', ], fieldsets, default_fieldset_name=_('Publication settings'), default_fieldset_position=1) return fieldsets def get_list_display(self, request): list_display = list(super(NewsboxExpiredAdmin, self).get_list_display(request)) if 'newsbox_publication_dates_short' not in list_display: try: index = list_display.index('newsbox_date_short') list_display[index] = 'newsbox_publication_dates_short' except ValueError: list_display.append('newsbox_publication_dates_short') return list_display class NewsboxSEOAdmin(NewsboxBaseAdmin): def get_fieldsets(self, request, obj=None): fieldsets = super(NewsboxSEOAdmin, self).get_fieldsets(request, obj) add_fields_to_fieldset( ['newsbox_indexed', 'newsbox_meta_description', 'newsbox_meta_keywords', ], fieldsets, default_fieldset_name=_('SEO Settings'), default_fieldset_classes=('collapse',), ) return fieldsets def get_list_display(self, request): list_display = super(NewsboxSEOAdmin, self).get_list_display(request) return list_display