qrurls/admin.py
author Tero Marttila <terom@fixme.fi>
Thu, 22 Aug 2013 02:44:51 +0300
changeset 56 96e1c616a955
parent 55 eb36c4d046c1
child 61 c961c16a19dd
permissions -rw-r--r--
move publishing schedule to URL model, make timetz aware
import datetime
import urllib

from django import http, shortcuts, forms
from django.conf import settings, urls
from django.contrib import admin
from django.core.urlresolvers import reverse
from django.utils import timezone, formats
import django.utils.html
import django.forms.models

from qrurls.models import URL, URLItem, URLImage

"""
    Private backend UI, using django-admin.
"""

class URLItemFormset (django.forms.models.BaseInlineFormSet) :
    """
        Uses the existing URLItems for the URLFeed to determine the initial publishing
        times for new items.
    """

    def __init__ (self, *args, **kwargs) :
        extra = kwargs.get('extra', 5)

        # hack to get at the URLFeed to determine our initial values..
        urlfeed = kwargs.get('instance')
        if not isinstance(urlfeed, URL) :
            urlfeed = None
        
        kwargs.update(initial=[
            # Either generic from today, or based on the actual urlfeed
            dict(published=publish) for publish in URLAdmin.publishing_schedule(urlfeed, count=extra)
        ])
        super(URLItemFormset, self).__init__(*args, **kwargs)

class URLItemInline (admin.TabularInline) :
    """
        Inline set of URLItems for an URLFeed.
    """

    model = URLItem
    formset = URLItemFormset

class URLAdmin (admin.ModelAdmin) :
    @classmethod
    def publishing_schedule (cls, urlfeed, count):
        """Yield URLItem.published values for feed, or defaults."""
        if urlfeed :
            date, time, offset = urlfeed.publishing_schedule()
        else:
            # no data... use defaults
            tznow = timezone.now() # with tzinfo
            
            date = tznow.date()
            time = tznow.timetz()
            offset = datetime.timedelta(days=1)
        
        return URL.apply_publishing_schedule(date, time, offset, count)

    def timezone (self, obj) :
        now = timezone.localtime(obj.now())
        tz = now.tzinfo
        td = now.tzinfo.utcoffset(now)

        if td :
            minutes, seconds = divmod(td.total_seconds(), 60)
            hours, minutes = divmod(minutes, 60)
            offset = "(UTC%+03d:%02d)" % (hours, minutes)
        else :
            offset = ""

        return u"%s %s" % (tz, offset)

    def qrcode_url (self, obj) :
        warn = None

        if obj.shorturl.upper() != obj.shorturl :
            warn = "Shorturl should be UPPERCASE for most compact QR code"

        return '<a href="{url}">{url}</a>{warn}'.format(
                url     = django.utils.html.escape(obj.qrcode_url()),
                warn    = '<p class="errornote">{warn}</p>'.format(warn=warn) if warn else '',
        )
    qrcode_url.allow_tags = True

    def qrcode_img (self, obj) :
        return '<img src="{img}" />'.format(
                img=django.utils.html.escape(obj.qrcode_img()),
        )
    qrcode_img.allow_tags = True
    
    # XXX: a whole bunch of ugly datetime-formatting for display...
    def active (self, obj) :
        item = obj.active_item()
        if item :
            return "%s %s" % (formats.localize(timezone.localtime(item.published)), item)
        else :
            return ""

    def now (self, obj) :
        return formats.localize(timezone.localtime(obj.now()))

    def upcoming (self, obj) :
        item = obj.upcoming_item()
        if item :
            return "%s %s" % (formats.localize(timezone.localtime(item.published)), item)
        else :
            return ""

    readonly_fields = (
        'timezone',
        'qrcode_url',
        'qrcode_img',
        'active_item',
        'upcoming_item',
        'active',
        'now',
        'upcoming',
    )
    list_display = (
        'get_absolute_url',
        'active_item',
        'upcoming_item',
    )
    fieldsets = (
        (None, {
            'fields': (
                'shorturl', 
                'publishing_time', 
                'timezone',
                'title',
            ),
        }),
        ("QRCode", {
            'fields': ('qrcode_url', 'qrcode_img'),
        }),
        ("Item publishing overview", {
            'fields': (
                'active',
                'now',
                'upcoming',
            ),
        }),
    )
    inlines = (URLItemInline, )

    ## Import
    class ImportForm (forms.Form) :
        shorturl = forms.ModelChoiceField(URL.objects)
        publishing_date = forms.DateField(widget=admin.widgets.AdminDateWidget)
        publishing_time = forms.TimeField(widget=admin.widgets.AdminTimeWidget)
        # Django forms doesn't really understand multi-value fields, so we just hack this ourselves.
        #image = forms.ImageField()

    def import_images_handler (self, url_feed, data, images=()) :
        """Custom for backend for mass-importing images into a feed."""
        
        # series of datetimes
        publishing = url_feed.apply_publishing_schedule(
            data['publishing_date'], data['publishing_time'], url_feed.publishing_offset, len(images))

        for image, publish in zip(images, publishing) :
            url_image = URLImage(image=image)
            url_image.save()

            url_item = URLItem(shorturl=url_feed, published=publish, image=url_image)
            url_item.save()

        return http.HttpResponseRedirect(reverse('admin:qrurls_url_change', args=(url_feed.id, )))

    def import_images (self, request, shorturl_id) :
        """Custom form frontend for mass-importing images into a feed."""
        url_feed = URL.objects.get(id=int(shorturl_id))
        
        if request.method == 'POST' :
            form = self.ImportForm(request.POST)
            images = request.FILES.getlist('image')

            if form.is_valid() :
                return self.import_images_handler(url_feed, form.cleaned_data, images)
        else :
            publishing_date, publishing_time, publishing_offset = url_feed.publishing_schedule()

            form = self.ImportForm(initial=dict(
                shorturl        = url_feed.id,
                publishing_date = publishing_date,
                publishing_time = publishing_time,
            ))
        
        return shortcuts.render(request, 'admin/qrurls_import_images.html', dict(
            current_app     = self.admin_site.name,

            media           = self.media + form.media,
            form_url        = reverse('admin:qrurls_import_images', kwargs=dict(shorturl_id=url_feed.id)),
            form            = form,
        ))

    def get_urls (self) :
        return urls.patterns('',
            urls.url(r'^(?P<shorturl_id>\d+)/import$', self.admin_site.admin_view(self.import_images),
                name='qrurls_import_images'),
        ) + super(URLAdmin, self).get_urls()

    def import_images_action (self, request, queryset) :
        url_feed, = [url_feed for url_feed in queryset]
        return http.HttpResponseRedirect(
                reverse('admin:qrurls_import_images', kwargs=dict(shorturl_id=url_feed.id)) # ) + '?' + urllib.urlencode(qs, doseq=True)
        )
    import_images_action.short_description = "Import images for selected feeds"
    
    actions = [import_images_action]

class URLItemAdmin (admin.ModelAdmin) :
    list_display = (
        'shorturl', 'published_state', 'get_absolute_url', 'image', 'published',
    )
    readonly_fields = ('published_state', 'published')
    fieldsets = (
        ("Publishing", {
            'fields': ('shorturl', 'published', ),
        }),
        ("Target", {
            'fields': ('url', 'image'),
        }),
    )

class URLImageAdmin (admin.ModelAdmin) :
    list_display = (
        'title',
        'uploaded',
    )
    # hide the "uploaded" field
    fields = (
        'image',
        'title',
    )
    readonly_fields = (
        'uploaded',
    )
    
    inlines = (URLItemInline, )

admin.site.register(URL, URLAdmin)
admin.site.register(URLItem, URLItemAdmin)
admin.site.register(URLImage, URLImageAdmin)