qrurls/models.py
author Tero Marttila <terom@fixme.fi>
Thu, 22 Aug 2013 02:44:51 +0300
changeset 56 96e1c616a955
parent 51 6f35a169ef01
child 63 9d1184cdbd16
permissions -rw-r--r--
move publishing schedule to URL model, make timetz aware
import datetime
import hashlib
import os.path

from django.db import models
import django.core.files.storage
from django.core.urlresolvers import reverse
import django.utils.http
from django.contrib.sites.models import get_current_site
from django.utils import timezone

QRCODE_API = 'https://chart.googleapis.com/chart?cht=qr&chs={width}x{height}&chl={url}&chld=H'
IMAGES_MEDIA = 'qrurls-images'

class SecretFileSystemStorage (django.core.files.storage.FileSystemStorage) :
    """
        Store files named by a sha1 hash of their contents.
    """

    HASH = hashlib.sha1
    
    def _hash (self, content) :
        """Compute hash for given UploadedFile (as a hex string)."""
        hash = self.HASH()

        for chunk in content.chunks() :
            hash.update(chunk)

        return hash.hexdigest()
    
    def save (self, name, content) :
        # Get the proper name for the file, as it will actually be saved.
        if name is None:
            name = content.name
        
        dirname, filename = os.path.split(name)
        basename, fileext = os.path.splitext(filename)
        
        # hash
        name = "%s/%s%s" % (dirname, self._hash(content), fileext)

        return super(SecretFileSystemStorage, self).save(name, content)

class URL(models.Model):
    shorturl = models.SlugField(unique=True,
            help_text="Changing this will break existing QR-codes!")
    publishing_time = models.TimeField(default=datetime.time(),
            help_text="Default time to publish new URLItems (in timezone)")
    title = models.CharField(max_length=1024, blank=True, null=True,
            help_text="Text to display together with images.")

    class Meta:
        verbose_name = u"URL Feed"
        verbose_name_plural = u"URL Feeds"

    def qrcode_img (self, size=512) :
        return QRCODE_API.format(
                width=size, height=size,
                url=django.utils.http.urlquote(self.qrcode_url()),
        )

    def qrcode_url (self) :
        return 'HTTP://{domain}{url}'.format(
                domain = get_current_site(None).domain.upper(),
                url = self.get_absolute_url(),
        )

    def get_absolute_url (self) :
        return reverse('shorturl', args=[self.shorturl])

    def now (self, now=None) :
        """
            Return database-compatible concept of "now".

            All datetimes are strictly stored and compared as UTC. Any
            timezone-aware logic should happen in the admin.
        """
        if now :
            return now
        else :
            return timezone.now()
    
    def active_item (self, now=None) :
        """Currently published URLItem."""
        now = self.now(now)

        try :
            return URLItem.objects.filter(shorturl=self, published__lt=now).order_by('-published')[0]
        except IndexError :
            return None

    def upcoming_item (self, now=None) :
        """Next-up to-be-published URLItem."""
        now = self.now(now)

        try :
            return URLItem.objects.filter(shorturl=self, published__gt=now).order_by('published')[0]
        except IndexError :
            return None

    def last_item (self) :
        """The last URLItem available."""

        try :
            return URLItem.objects.filter(shorturl=self).order_by('-published')[0]
        except IndexError :
            return None

    @property
    def publishing_timetz (self) :
        """publishing_time, with tzinfo on the correct timezone."""
        return self.publishing_time.replace(tzinfo=timezone.get_current_timezone())
    
    # XXX: hardcoded for now
    publishing_offset = datetime.timedelta(days=1)

    def publishing_schedule (self) :
        """Calculate initial URLItem.published values for feed."""

        # following the last item in the queue
        item = self.last_item()

        if item and item.published > self.now():
            # starting from the following day
            date = item.published.date() + self.publishing_offset
        else :
            # defaults to today
            date = self.now().date()
        
        return date, self.publishing_timetz, self.publishing_offset
    
    @classmethod
    def apply_publishing_schedule (self, date, time, offset, count) :
        """Yield publishing times off given date/time to offset * count."""
        for index in xrange(0, count) :
            yield datetime.datetime.combine(date + offset * index, time)

    def __unicode__ (self) :
        return self.shorturl

class URLImage(models.Model):
    image = models.ImageField(upload_to=IMAGES_MEDIA, storage=SecretFileSystemStorage())
    title = models.CharField(max_length=1024, blank=True)
    uploaded = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name = u"URL Image"
        verbose_name_plural = u"URL Images"
        ordering = ['uploaded']

    def get_absolute_url (self) :
        return self.image.url

    def __unicode__ (self) :
        return "[%s] %s" % (self.uploaded.strftime("%Y-%m-%d"), self.title)

class URLItem(models.Model):
    shorturl = models.ForeignKey(URL)
    published = models.DateTimeField() # UTC

    # either-or
    url = models.URLField(blank=True) # populated from image
    image = models.ForeignKey(URLImage, null=True, blank=True)
    
    class Meta:
        verbose_name = u"URL Item"
        verbose_name_plural = u"URL Items"
        ordering = ['published']

    def get_absolute_url (self) :
        if self.url :
            return self.url
        elif self.shorturl and self.id :
            return reverse('shorturl_item', kwargs=dict(shorturl=self.shorturl, item_id=self.id))
        else :
            return None
   
    def published_age (self) :
        now = self.shorturl.now() # UTC

        if now > self.published:
            td = now - self.published
        else :
            td = self.published - now

        days, seconds = td.days, td.seconds
        m, s = divmod(seconds, 60)
        h, m = divmod(m, 60)

        return (self.published < now), days, "{h}h {m}m {s}s".format(h=h, m=m, s=s)

    def published_state (self) :
        date = self.published.strftime("%Y-%m-%d")
        published, days, age = self.published_age()

        if published and days :
            return "[{date}]".format(date=date)
        elif published :
            return "[{age}]".format(age=age)
        elif days :
            return "({when})".format(when=date)
        else :
            return "({age})".format(age=age)

    def __unicode__ (self) :
        return u"{published_state} {url}".format(
                published_state=self.published_state(),
                url=self.get_absolute_url(),
        )