Skip to content
Snippets Groups Projects
admin.py 17 KiB
Newer Older
Olivier Le Brouster's avatar
Olivier Le Brouster committed
# -*- coding: utf-8 -*-
Dylann Cordel's avatar
Dylann Cordel committed
# Future imports
Olivier Le Brouster's avatar
Olivier Le Brouster committed
from __future__ import unicode_literals
Dylann Cordel's avatar
Dylann Cordel committed

# Standard libs
Olivier Le Brouster's avatar
Olivier Le Brouster committed
import copy
import itertools
Olivier Le Brouster's avatar
Olivier Le Brouster committed

Dylann Cordel's avatar
Dylann Cordel committed
# Django imports
Dylann Cordel's avatar
Dylann Cordel committed
from django.conf.urls import url
Dylann Cordel's avatar
Dylann Cordel committed
from django.contrib import admin
from django.contrib import messages
from django.contrib.admin.models import CHANGE
from django.contrib.admin.models import LogEntry
Olivier Le Brouster's avatar
Olivier Le Brouster committed
from django.contrib.contenttypes.models import ContentType
Dylann Cordel's avatar
Dylann Cordel committed
from django.templatetags.static import static

Dylann Cordel's avatar
Dylann Cordel committed
try:
Dylann Cordel's avatar
Dylann Cordel committed
    # Django imports
Dylann Cordel's avatar
Dylann Cordel committed
    from django.urls import reverse
except ImportError:
    from django.core.urlresolvers import reverse
Dylann Cordel's avatar
Dylann Cordel committed

# Django imports
from django.http import HttpResponseForbidden
from django.http import HttpResponseRedirect
Olivier Le Brouster's avatar
Olivier Le Brouster committed
from django.shortcuts import get_object_or_404
Dylann Cordel's avatar
Dylann Cordel committed
from django.utils import formats
from django.utils.html import format_html
Dylann Cordel's avatar
Dylann Cordel committed
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy

# Third Party
import six
Dylann Cordel's avatar
Dylann Cordel committed

Olivier Le Brouster's avatar
Olivier Le Brouster committed
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,
Olivier Le Brouster's avatar
Olivier Le Brouster committed
    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):

Dylann Cordel's avatar
Dylann Cordel committed
    list_filter = ('newsbox_publication_start_date', 'newsbox_status', )
Christine Ho's avatar
Christine Ho committed
    list_display = ('get_newsbox_title',)
Olivier Le Brouster's avatar
Olivier Le Brouster committed
    list_display_links = ('get_newsbox_title',)
    fieldsets = [
        (None, {
Dylann Cordel's avatar
Dylann Cordel committed
            'fields': ['newsbox_title', 'newsbox_slug', 'newsbox_date', 'newsbox_status', ], }),
Olivier Le Brouster's avatar
Olivier Le Brouster committed
        (_('Content'), {
            'fields': ['newsbox_summary', 'newsbox_body'], }), ]
    actions = ['publish', 'unpublish']
    save_as = True

    def get_list_display(self, request):
Christine HO's avatar
Christine HO committed
        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):
Dylann Cordel's avatar
Dylann Cordel committed
        pfields = super(NewsboxBaseAdmin, self).get_prepopulated_fields(request, object) or {}
        pfields.update({'newsbox_slug': ('newsbox_title',)})
        return pfields
    def changestatus_link(self, obj):
Dylann Cordel's avatar
Dylann Cordel committed
        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)
Christine HO's avatar
Christine HO committed
        return format_html(
            '<a href="{}" title="{}"><img src="{}" alt="{}" /></a>',
            reverse('admin:%s' % url_name, args=[obj.pk, ]),
Christine HO's avatar
Christine HO committed
            title,
            icon_url,
            obj.get_newsbox_status_display()
        )
Dylann Cordel's avatar
Dylann Cordel committed
    changestatus_link.admin_order_field = 'newsbox_status'
    changestatus_link.short_description = _('published')

    def newsbox_date_short(self, obj):
Christine HO's avatar
Christine HO committed
        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')

Olivier Le Brouster's avatar
Olivier Le Brouster committed
    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)
Dylann Cordel's avatar
Dylann Cordel committed
        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),
Dylann Cordel's avatar
Dylann Cordel committed
                name='%sunpublish_one' % url_prefix,)
        ] + super(NewsboxBaseAdmin, self).get_urls()
Olivier Le Brouster's avatar
Olivier Le Brouster committed

    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):
Olivier Le Brouster's avatar
Olivier Le Brouster committed
            return HttpResponseForbidden(
                _('You do not have permission to publish this %s')
                % (six.text_type(self.model._meta.verbose_name),))

Dylann Cordel's avatar
Dylann Cordel committed
        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
Dylann Cordel's avatar
Dylann Cordel committed
        log_msg = _('publication status changed from %(old)s to %(new)s') % {
            'old': old_status,
            'new': obj.newsbox_status,
Dylann Cordel's avatar
Dylann Cordel committed
        }
        if obj.newsbox_status == obj.STATUS_PUBLISHED:
            msg = _('The %(objtype)s "%(objtitle)s" has been successfully published')
Olivier Le Brouster's avatar
Olivier Le Brouster committed
        else:
Dylann Cordel's avatar
Dylann Cordel committed
            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)})
Olivier Le Brouster's avatar
Olivier Le Brouster committed
        LogEntry.objects.log_action(
            user_id=request.user.id,
            content_type_id=ContentType.objects.get_for_model(self.model).pk,
            object_id=news_id,
Olivier Le Brouster's avatar
Olivier Le Brouster committed
            action_flag=CHANGE,
Olivier Le Brouster's avatar
Olivier Le Brouster committed
        )
        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', '../../'))
Olivier Le Brouster's avatar
Olivier Le Brouster committed

    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:
Olivier Le Brouster's avatar
Olivier Le Brouster committed
                continue

            if not obj.has_publish_permission(request.user):
Olivier Le Brouster's avatar
Olivier Le Brouster committed
                messages.error(
                    request,
                    _(
                        'You do not have permission to publish the '
                        '%(objtype)s "%(objtitle)s"'
                    ) % {
                        'objtype': six.text_type(self.model._meta.verbose_name),
Olivier Le Brouster's avatar
Olivier Le Brouster committed
                    }
                )
                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'),)
Olivier Le Brouster's avatar
Olivier Le Brouster committed
            ok += 1
        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(
Dylann Cordel's avatar
Dylann Cordel committed
                '%(nb)d %(objtype)s has been published',
                '%(nb)d %(objtype)s have been published',
Dylann Cordel's avatar
Dylann Cordel committed
                       'objtype': self.model._meta.verbose_name_plural,})
Olivier Le Brouster's avatar
Olivier Le Brouster committed
    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:
Olivier Le Brouster's avatar
Olivier Le Brouster committed
                continue

            if not obj.has_publish_permission(request.user):
Olivier Le Brouster's avatar
Olivier Le Brouster committed
                messages.error(
                    request,
                    _(
                        'You do not have permission to unpublish the '
                        '%(objtype)s "%(objtitle)s"'
                    ) % {
                        'objtype': six.text_type(self.model._meta.verbose_name),
Olivier Le Brouster's avatar
Olivier Le Brouster committed
                    }
                )
                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'),)
Olivier Le Brouster's avatar
Olivier Le Brouster committed
            ok += 1
        if ok == 1:
            messages.success(request, _('The %(objtype)s "%(objtitle)s" has been unpublished') % {
Olivier Le Brouster's avatar
Olivier Le Brouster committed
                'objtype': self.model._meta.verbose_name,
                'objtitle': last_obj_ok, })
        else:
            messages.success(request, ungettext_lazy(
Dylann Cordel's avatar
Dylann Cordel committed
                '%(nb)d %(objtype)s has been unpublished',
                '%(nb)d %(objtype)s have been unpublished',
Dylann Cordel's avatar
Dylann Cordel committed
                       '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):
Dylann Cordel's avatar
Dylann Cordel committed
        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)


Olivier Le Brouster's avatar
Olivier Le Brouster committed
class NewsboxAdmin(NewsboxBaseAdmin):
    pass


class NewsboxExpiredAdmin(NewsboxBaseAdmin):

    def newsbox_publication_dates_short(self, obj):
        output = []
        title = []
        css_class = []
Dylann Cordel's avatar
Dylann Cordel committed
        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'))
Dylann Cordel's avatar
Dylann Cordel committed
            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)
Dylann Cordel's avatar
Dylann Cordel committed
        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>' % (
Christine HO's avatar
Christine HO committed
            css_class, title, output))
    newsbox_publication_dates_short.short_description = _('publication')
Olivier Le Brouster's avatar
Olivier Le Brouster committed
    def get_fieldsets(self, request, obj=None):
        fieldsets = super(NewsboxExpiredAdmin, self).get_fieldsets(request, obj)
Dylann Cordel's avatar
Dylann Cordel committed
        for fieldset in fieldsets:
            if 'newsbox_publication_end_date' in fieldset[1]['fields']:
                return fieldsets
        remove_field_from_fieldsets('newsbox_status', fieldsets)
Olivier Le Brouster's avatar
Olivier Le Brouster committed
        add_fields_to_fieldset(
Dylann Cordel's avatar
Dylann Cordel committed
            ['newsbox_status',
Olivier Le Brouster's avatar
Olivier Le Brouster committed
             'newsbox_publication_start_date',
             'newsbox_publication_end_date', ],
            fieldsets,
Dylann Cordel's avatar
Dylann Cordel committed
            default_fieldset_name=_('Publication settings'),
Olivier Le Brouster's avatar
Olivier Le Brouster committed
            default_fieldset_position=1)

        return fieldsets

    def get_list_display(self, request):
        list_display = list(super(NewsboxExpiredAdmin, self).get_list_display(request))
Dylann Cordel's avatar
Dylann Cordel committed
        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')
Olivier Le Brouster's avatar
Olivier Le Brouster committed
        return list_display

Dylann Cordel's avatar
Dylann Cordel committed

Olivier Le Brouster's avatar
Olivier Le Brouster committed
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