Mercurial > public > madeira
changeset 41:63e4211628e1
Rename the mysite directory to madeira.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Tue, 14 Feb 2012 19:15:16 -0600 |
parents | 25e00d1b99bf |
children | 82f65fb5e260 |
files | madeira/__init__.py madeira/apache/madeira.wsgi madeira/band/__init__.py madeira/band/admin.py madeira/band/admin_views.py madeira/band/models.py madeira/band/urls.py madeira/band/views.py madeira/manage.py madeira/photologue/LICENSE.txt madeira/photologue/README.txt madeira/photologue/__init__.py madeira/photologue/admin.py madeira/photologue/locale/pl/LC_MESSAGES/django.mo madeira/photologue/locale/pl/LC_MESSAGES/django.po madeira/photologue/management/__init__.py madeira/photologue/management/commands/__init__.py madeira/photologue/management/commands/plcache.py madeira/photologue/management/commands/plcreatesize.py madeira/photologue/management/commands/plflush.py madeira/photologue/management/commands/plinit.py madeira/photologue/models.py madeira/photologue/res/sample.jpg madeira/photologue/res/test_landscape.jpg madeira/photologue/res/test_portrait.jpg madeira/photologue/res/test_square.jpg madeira/photologue/templates/photologue/gallery_archive.html madeira/photologue/templates/photologue/gallery_archive_day.html madeira/photologue/templates/photologue/gallery_archive_month.html madeira/photologue/templates/photologue/gallery_archive_year.html madeira/photologue/templates/photologue/gallery_detail.html madeira/photologue/templates/photologue/gallery_list.html madeira/photologue/templates/photologue/photo_archive.html madeira/photologue/templates/photologue/photo_archive_day.html madeira/photologue/templates/photologue/photo_archive_month.html madeira/photologue/templates/photologue/photo_archive_year.html madeira/photologue/templates/photologue/photo_detail.html madeira/photologue/templates/photologue/photo_list.html madeira/photologue/templates/photologue/root.html madeira/photologue/tests.py madeira/photologue/urls.py madeira/photologue/utils/EXIF.py madeira/photologue/utils/__init__.py madeira/photologue/utils/reflection.py madeira/photologue/utils/watermark.py madeira/pl-admin.py madeira/settings/__init__.py madeira/settings/base.py madeira/settings/local.py madeira/settings/production.py madeira/templates/404.html madeira/templates/500.html madeira/templates/admin/band/email.html madeira/templates/admin/band/email_sent.html madeira/templates/admin/base_site.html madeira/templates/admin/index.html madeira/templates/band/base.html madeira/templates/band/bio.html madeira/templates/band/buy.html madeira/templates/band/contact.html madeira/templates/band/email_subscribe.txt madeira/templates/band/email_unsubscribe.txt madeira/templates/band/flyers.html madeira/templates/band/gigs.html madeira/templates/band/index.html madeira/templates/band/mail.html madeira/templates/band/mail_confirm.html madeira/templates/band/mail_not_found.html madeira/templates/band/mail_thanks.html madeira/templates/band/mail_unsubscribe.html madeira/templates/band/news.html madeira/templates/band/photo_detail.html madeira/templates/band/photos.html madeira/templates/band/press.html madeira/templates/band/press_detail.html madeira/templates/band/songs.html madeira/templates/band/video_detail.html madeira/templates/band/videos.html madeira/templates/flatpages/default.html madeira/urls.py mysite/__init__.py mysite/apache/madeira.wsgi mysite/band/__init__.py mysite/band/admin.py mysite/band/admin_views.py mysite/band/models.py mysite/band/urls.py mysite/band/views.py mysite/manage.py mysite/photologue/LICENSE.txt mysite/photologue/README.txt mysite/photologue/__init__.py mysite/photologue/admin.py mysite/photologue/locale/pl/LC_MESSAGES/django.mo mysite/photologue/locale/pl/LC_MESSAGES/django.po mysite/photologue/management/__init__.py mysite/photologue/management/commands/__init__.py mysite/photologue/management/commands/plcache.py mysite/photologue/management/commands/plcreatesize.py mysite/photologue/management/commands/plflush.py mysite/photologue/management/commands/plinit.py mysite/photologue/models.py mysite/photologue/res/sample.jpg mysite/photologue/res/test_landscape.jpg mysite/photologue/res/test_portrait.jpg mysite/photologue/res/test_square.jpg mysite/photologue/templates/photologue/gallery_archive.html mysite/photologue/templates/photologue/gallery_archive_day.html mysite/photologue/templates/photologue/gallery_archive_month.html mysite/photologue/templates/photologue/gallery_archive_year.html mysite/photologue/templates/photologue/gallery_detail.html mysite/photologue/templates/photologue/gallery_list.html mysite/photologue/templates/photologue/photo_archive.html mysite/photologue/templates/photologue/photo_archive_day.html mysite/photologue/templates/photologue/photo_archive_month.html mysite/photologue/templates/photologue/photo_archive_year.html mysite/photologue/templates/photologue/photo_detail.html mysite/photologue/templates/photologue/photo_list.html mysite/photologue/templates/photologue/root.html mysite/photologue/tests.py mysite/photologue/urls.py mysite/photologue/utils/EXIF.py mysite/photologue/utils/__init__.py mysite/photologue/utils/reflection.py mysite/photologue/utils/watermark.py mysite/pl-admin.py mysite/settings/__init__.py mysite/settings/base.py mysite/settings/local.py mysite/settings/production.py mysite/templates/404.html mysite/templates/500.html mysite/templates/admin/band/email.html mysite/templates/admin/band/email_sent.html mysite/templates/admin/base_site.html mysite/templates/admin/index.html mysite/templates/band/base.html mysite/templates/band/bio.html mysite/templates/band/buy.html mysite/templates/band/contact.html mysite/templates/band/email_subscribe.txt mysite/templates/band/email_unsubscribe.txt mysite/templates/band/flyers.html mysite/templates/band/gigs.html mysite/templates/band/index.html mysite/templates/band/mail.html mysite/templates/band/mail_confirm.html mysite/templates/band/mail_not_found.html mysite/templates/band/mail_thanks.html mysite/templates/band/mail_unsubscribe.html mysite/templates/band/news.html mysite/templates/band/photo_detail.html mysite/templates/band/photos.html mysite/templates/band/press.html mysite/templates/band/press_detail.html mysite/templates/band/songs.html mysite/templates/band/video_detail.html mysite/templates/band/videos.html mysite/templates/flatpages/default.html mysite/urls.py |
diffstat | 150 files changed, 6185 insertions(+), 6185 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/apache/madeira.wsgi Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,37 @@ +import os +import sys + +OFFLINE = False + +sys.path.append('/home/var/django-sites/madeira/django-trunk') +sys.path.append('/home/var/django-sites/madeira/madeira-trunk') +sys.path.append('/home/var/django-sites/madeira/madeira-trunk/mysite') +os.environ['PYTHON_EGG_CACHE'] = '/home/var/django-sites/madeira/eggs/' + + +def offline_handler(environ, start_response): + wsgi_dir = os.path.dirname(__file__) + sys.path.append(wsgi_dir) + + offline_file = os.path.join(wsgi_dir, '..', 'templates', 'offline.html') + if os.path.exists(offline_file): + response_headers = [('Content-type','text/html')] + response = open(offline_file).read() + else: + response_headers = [('Content-type','text/plain')] + response = 'themadeira.net website maintenance in progress; please check back soon.' + + if environ['REQUEST_METHOD'] == 'GET': + status = '503 Service Unavailable' + else: + status = '405 Method Not Allowed' + start_response(status, response_headers) + return [response] + + +if not OFFLINE: + os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings.production' + import django.core.handlers.wsgi + application = django.core.handlers.wsgi.WSGIHandler() +else: + application = offline_handler
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/band/admin.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,244 @@ +####################################################################### +# +# PyBand Copyright (C) 2008 by Brian Neal +# +####################################################################### + +from django.contrib import admin + +from band.models import Article +from band.models import Album +from band.models import Album_Merchant +from band.models import Album_Track +from band.models import Band +from band.models import City +from band.models import Country +from band.models import Fan +from band.models import Gear +from band.models import Gig +from band.models import Label_Release +from band.models import Member +from band.models import Merchandise +from band.models import Mp3 +from band.models import Mp3_Set +from band.models import News +from band.models import Record_Label +from band.models import SiteConfig +from band.models import State +from band.models import Venue +from band.models import Video +from band.models import Video_Set + +####################################################################### + +admin.site.register(Video) + +####################################################################### + +class SiteConfigAdmin(admin.ModelAdmin): + list_display = ('band_name', 'url', 'contact_email') + fieldsets = ( + (None, { 'fields' : ('band_name', 'url', 'contact_email', 'intro_text', 'ordering_info', + 'intro_photo') }), + ) + +admin.site.register(SiteConfig, SiteConfigAdmin) + +####################################################################### + +class GearInline(admin.TabularInline): + model = Gear + +class GearAdmin(admin.ModelAdmin): + list_display = ('item', 'member') + list_filter = ('member', ) + +admin.site.register(Gear, GearAdmin) + +####################################################################### + +class MemberAdmin(admin.ModelAdmin): + list_display = ('name', 'instrument', 'is_active') + inlines = [ + GearInline, + ] + +admin.site.register(Member, MemberAdmin) + +####################################################################### + +class CityInline(admin.TabularInline): + model = City + +class CityAdmin(admin.ModelAdmin): + list_display = ('name', 'state', 'country') + list_filter = ('state', ) + search_fields = ('name', ) + +admin.site.register(City, CityAdmin) + +####################################################################### + +admin.site.register(Country) + +####################################################################### + +class StateAdmin(admin.ModelAdmin): + inlines = [ + CityInline, + ] + +admin.site.register(State, StateAdmin) + +####################################################################### + +class VenueAdmin(admin.ModelAdmin): + list_filter = ('city', ) + list_display = ('name', 'city', ) + search_fields = ('name', ) + +admin.site.register(Venue, VenueAdmin) + +####################################################################### + +class BandAdmin(admin.ModelAdmin): + search_fields = ('name', ) + +admin.site.register(Band, BandAdmin) + +####################################################################### + +class GigAdmin(admin.ModelAdmin): + list_filter = ('date', 'venue') + save_on_top = True + filter_horizontal = ('bands', ) + +admin.site.register(Gig, GigAdmin) + +####################################################################### + +class NewsAdmin(admin.ModelAdmin): + save_on_top = True + list_filter = ('date', ) + list_display = ('date', 'title') + search_fields = ('text', 'title') + +admin.site.register(News, NewsAdmin) + +####################################################################### + +class ArticleAdmin(admin.ModelAdmin): + save_on_top = True + list_filter = ('date', ) + list_display = ('title', 'date') + search_fields = ('text', 'title') + +admin.site.register(Article, ArticleAdmin) + +####################################################################### + +class Mp3Inline(admin.TabularInline): + model = Mp3 + +class Mp3Admin(admin.ModelAdmin): + prepopulated_fields = {'slug' : ('title', 'desc')} + +admin.site.register(Mp3, Mp3Admin) + +####################################################################### + +class Mp3_SetAdmin(admin.ModelAdmin): + list_filter = ('date', ) + list_display = ('title', 'date') + inlines = [ + Mp3Inline, + ] + +admin.site.register(Mp3_Set, Mp3_SetAdmin) + +####################################################################### + +class VideoInline(admin.TabularInline): + model = Video + +class Video_SetAdmin(admin.ModelAdmin): + list_filter = ('date', ) + list_display = ('title', 'date') + inlines = [ + VideoInline, + ] + +admin.site.register(Video_Set, Video_SetAdmin) + +####################################################################### + +class Album_TrackInline(admin.TabularInline): + model = Album_Track + +class Album_TrackAdmin(admin.ModelAdmin): + list_display = ('track_name', 'album') + list_filter = ('album', ) + +admin.site.register(Album_Track, Album_TrackAdmin) + +####################################################################### + +class Label_ReleaseInline(admin.TabularInline): + model = Label_Release + +class Label_ReleaseAdmin(admin.ModelAdmin): + list_display = ('catalog_number', 'album', 'record_label', 'release_date') + list_filter = ('record_label', 'album') + +admin.site.register(Label_Release, Label_ReleaseAdmin) + +####################################################################### + +class Record_LabelAdmin(admin.ModelAdmin): + inlines = [ + Label_ReleaseInline, + ] + +admin.site.register(Record_Label, Record_LabelAdmin) + +####################################################################### + +class Album_MerchantInline(admin.TabularInline): + model = Album_Merchant + +class Album_MerchantAdmin(admin.ModelAdmin): + list_display = ('name', 'album') + list_filter = ('album', ) + +admin.site.register(Album_Merchant, Album_MerchantAdmin) + +####################################################################### + +class AlbumAdmin(admin.ModelAdmin): + save_on_top = True + inlines = [ + Album_TrackInline, + Label_ReleaseInline, + Album_MerchantInline, + ] + +admin.site.register(Album, AlbumAdmin) + +####################################################################### + +class MerchandiseAdmin(admin.ModelAdmin): + list_display = ('name', 'price', 'in_stock') + list_filter = ('in_stock', ) + +admin.site.register(Merchandise, MerchandiseAdmin) + +####################################################################### + +class FanAdmin(admin.ModelAdmin): + list_display = ('name', 'email', 'current_status') + search_fields = ('name', 'email') + +admin.site.register(Fan, FanAdmin) + +####################################################################### +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/band/admin_views.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,77 @@ +####################################################################### +# +# PyBand Copyright (C) 2008 by Brian Neal +# +####################################################################### +from django import forms +from django.core.urlresolvers import reverse +from django.core.mail import EmailMessage +from django.template import RequestContext +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response +from django.contrib.admin.views.decorators import staff_member_required + +from band.models import SiteConfig +from band.models import Fan + +####################################################################### + +unsubscribeText = ''' + +---- +You are receiving this message because you are subscribed to our mailing list. +If you would like to unsubscribe please visit %s. +''' + +####################################################################### + +class EmailForm(forms.Form): + subject = forms.CharField(max_length = 255, required = True, label = 'Subject:', + widget = forms.TextInput(attrs = {'class' : 'vTextField required', 'size' : '60'})) + message = forms.CharField(label = 'Message:', + widget = forms.Textarea(attrs = {'class' : 'vLargeTextField required'})) + +####################################################################### + +def email_sent(request): + return render_to_response('admin/band/email_sent.html', + {}, + context_instance = RequestContext(request)) + +####################################################################### + +def email(request): + + config = SiteConfig.objects.get(pk = 1) + bandTag = '[%s] ' % (config.band_name, ) + + if request.method == 'POST': + form = EmailForm(request.POST) + if form.is_valid(): + subject = form.cleaned_data['subject'] + message = form.cleaned_data['message'] + + unsubscribeUrl = config.url + if unsubscribeUrl[-1] != '/': + unsubscribeUrl += '/' + unsubscribeUrl += 'mail' + + footer = unsubscribeText % (unsubscribeUrl, ) + message += footer + + fans = Fan.objects.all() + bcc = [fan.email for fan in fans] + + email = EmailMessage(subject, message, config.contact_email, + [config.contact_email], bcc) + email.send() + return HttpResponseRedirect(reverse(email_sent)) + + else: + form = EmailForm(initial = { 'subject' : bandTag }) + + return render_to_response('admin/band/email.html', + { 'form' : form }, + context_instance = RequestContext(request)) + +email = staff_member_required(email)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/band/models.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,393 @@ +from django.db import models +from django.contrib.localflavor.us.models import USStateField +from django.contrib.localflavor.us.models import PhoneNumberField + +from photologue.models import Photo +import datetime +import random +import string + +####################################################################### + +class SiteConfig(models.Model): + band_name = models.CharField(max_length = 50) + url = models.URLField(verify_exists = False, max_length = 200) + contact_email = models.EmailField() + ordering_info = models.TextField(help_text = 'Enter instructions on how to order merchandise here') + intro_text = models.TextField(help_text = 'This text appears on the home page.') + intro_photo = models.ForeignKey(Photo) + + def __unicode__(self): + return self.band_name + + class Meta: + verbose_name = "Site Configuration" + verbose_name_plural = "Site Configuration" + +####################################################################### + +class Member(models.Model): + name = models.CharField(max_length = 50, db_index = True) + nickname = models.CharField(max_length = 50, blank = True) + instrument = models.CharField(max_length = 255) + bio = models.TextField(blank = True) + photo = models.FileField(upload_to = 'images/bio/', blank = True) + order = models.SmallIntegerField(help_text = '''Controls order of display on the bio page, lower numbers displayed + first''') + is_active = models.BooleanField(db_index = True) + start_date = models.DateField() + end_date = models.DateField(blank = True, help_text = 'Only used if the member is not active', + default = datetime.date(1985, 1, 1)) + email = models.EmailField() + + def __unicode__(self): + return self.name + + class Meta: + ordering = ('-is_active', 'name') + +####################################################################### + +class Gear(models.Model): + member = models.ForeignKey(Member) + item = models.CharField(max_length = 255) + + def __unicode__(self): + return self.item + + class Meta: + verbose_name_plural = 'Gear List' + +####################################################################### + +class Country(models.Model): + name = models.CharField(max_length=64) + + class Meta: + ordering = ('name', ) + verbose_name_plural = 'Countries' + + def __unicode__(self): + return self.name + +####################################################################### + +class State(models.Model): + name = models.CharField(max_length = 16) + abbrev = USStateField() + + class Meta: + ordering = ('name', ) + + def __unicode__(self): + return self.name + +####################################################################### + +class City(models.Model): + name = models.CharField(max_length = 50) + state = models.ForeignKey(State, null = True, blank = True) + country = models.ForeignKey(Country, null=True, blank=True) + + class Meta: + verbose_name_plural = 'Cities' + ordering = ('name', ) + + def __unicode__(self): + if self.state: + return self.name + u', ' + self.state.abbrev + return self.name + +####################################################################### + +class Venue(models.Model): + name = models.CharField(max_length = 50, db_index = True) + url = models.URLField(verify_exists = False, blank = True) + address = models.CharField(max_length = 255, blank = True) + phone = PhoneNumberField(help_text = "Format: XXX-XXX-XXXX", blank = True) + city = models.ForeignKey(City) + + class Meta: + ordering = ('name', ) + + def __unicode__(self): + return self.name + +####################################################################### + +class Band(models.Model): + name = models.CharField(max_length = 64) + url = models.URLField(verify_exists = False, blank = True) + + class Meta: + ordering = ('name', ) + + def __unicode__(self): + return self.name + +####################################################################### + +class Gig(models.Model): + title = models.CharField(max_length = 50, blank = True, help_text = "Optional; e.g. Some Festival") + url = models.URLField(verify_exists = False, blank = True, help_text = "Optional; e.g. Some Festival's Website") + date = models.DateField(db_index = True) + time = models.TimeField(null = True, blank = True) + venue = models.ForeignKey(Venue, null = True, blank = True) + notes = models.TextField(blank = True) + bands = models.ManyToManyField(Band, blank = True) + flyer = models.ForeignKey(Photo, null = True, blank = True) + + def __unicode__(self): + if self.title: + return u'%s %s %s' % (self.date.strftime('%m/%d/%Y'), self.title, self.venue.name) + elif self.venue: + return u'%s %s' % (self.date.strftime('%m/%d/%Y'), self.venue.name) + else: + return u'' + self.date.strftime('%m/%d/%Y') + + class Meta: + ordering = ('-date', 'time') + +####################################################################### + +class News(models.Model): + title = models.CharField(max_length = 64, blank = True) + date = models.DateField(db_index = True) + author = models.CharField(max_length = 50, blank = True) + text = models.TextField() + markup_enabled = models.BooleanField(default = True, + help_text = 'Check this box to allow Textile style markup in the text field') + photo = models.FileField(upload_to = 'images/news/%Y/%m/%d/', blank = True) + photo_caption = models.CharField(max_length = 50, blank = True) + + def __unicode__(self): + return u'%s %s' % (self.date.strftime('%m/%d/%Y'), self.title) + + class Meta: + ordering = ('-date', ) + verbose_name_plural = "News" + +####################################################################### + +class Article(models.Model): + title = models.CharField(max_length = 64) + date = models.DateField(db_index = True) + text = models.TextField() + markup_enabled = models.BooleanField(default = True, + help_text = 'Check this box to allow Textile style markup in the text field') + source = models.TextField(help_text = '''Enter the source/author for the article, copyright info, etc; it will appear under + the article.''') + url = models.URLField(blank = True, help_text = 'Link to original article; optional') + pdf = models.FileField(upload_to = 'pdf/articles/%Y/%m/%d/', blank = True, + help_text = '''If you want to make the original article available as a PDF download, you may upload it + here.''') + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('date', ) + +####################################################################### + +class Mp3_Set(models.Model): + date = models.DateField(auto_now_add = True, editable = False) + title = models.CharField(max_length = 64) + text = models.TextField() + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('date', ) + verbose_name = "MP3 Set" + +####################################################################### + +class Mp3(models.Model): + mp3_set = models.ForeignKey(Mp3_Set) + title = models.CharField(max_length = 64) + desc = models.CharField(max_length = 128, blank = True) + file = models.FileField(upload_to = 'mp3s/%Y/%m/%d/') + slug = models.SlugField(unique = True) + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('title', ) + verbose_name = "MP3" + +####################################################################### + +class Video_Set(models.Model): + date = models.DateField(blank=True) + title = models.CharField(max_length = 64) + text = models.TextField() + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('date', ) + verbose_name = "Video Set" + + def save(self, *args, **kwargs): + if not self.id: + self.date = datetime.date.today() + + super(Video_Set, self).save(*args, **kwargs) + +####################################################################### + +class Video(models.Model): + video_set = models.ForeignKey(Video_Set) + title = models.CharField(max_length = 64) + embed_code = models.CharField(max_length = 1024) + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('title', ) + +####################################################################### + +class Record_Label(models.Model): + name = models.CharField(max_length = 64) + url = models.URLField(verify_exists = False, max_length = 200) + + def __unicode__(self): + return self.name + + class Meta: + verbose_name = 'Record Label' + +####################################################################### + +class Album(models.Model): + title = models.CharField(max_length = 64) + photo = models.ForeignKey(Photo) + desc = models.TextField(blank = True) + + def __unicode__(self): + return self.title + + class Meta: + pass + +####################################################################### + +class Album_Track(models.Model): + album = models.ForeignKey(Album) + track_number = models.SmallIntegerField() + track_name = models.CharField(max_length = 64) + + def __unicode__(self): + return self.track_name + + class Meta: + verbose_name = 'Album Track' + ordering = ('album', 'track_number', ) + +####################################################################### + +class Label_Release(models.Model): + record_label = models.ForeignKey(Record_Label) + album = models.ForeignKey(Album) + catalog_number = models.CharField(max_length = 32) + release_date = models.DateField() + + def __unicode__(self): + return u'%s %s %s' % (self.record_label.name, self.album.title, self.catalog_number) + + class Meta: + verbose_name = 'Label Release' + +####################################################################### + +class Album_Merchant(models.Model): + album = models.ForeignKey(Album) + name = models.CharField(max_length = 64) + url = models.URLField(verify_exists = False, max_length = 200) + + def __unicode__(self): + return u'%s (%s)' % (self.name, self.album.title) + + class Meta: + verbose_name = 'Album Merchant' + ordering = ('name', ) + +####################################################################### + +class Merchandise(models.Model): + name = models.CharField(max_length = 64) + desc = models.TextField() + price = models.DecimalField(max_digits = 5, decimal_places = 2) + in_stock = models.BooleanField() + photo = models.ForeignKey(Photo) + + def __unicode__(self): + return self.name + + class Meta: + verbose_name_plural = "Merchandise" + +####################################################################### + +class Fan(models.Model): + statusCodes = (('P', 'Pending'), ('A', 'Active'), ('L', 'Leaving')) + keyLength = 16 + + name = models.CharField(max_length = 32, blank = True) + email = models.EmailField(db_index = True) + location = models.CharField(max_length = 64, blank = True) + status = models.CharField(max_length = 1, choices = statusCodes, default = 'A', + editable = False, db_index = True) + key = models.CharField(max_length = keyLength, editable = False, blank = True, db_index = True) + status_date = models.DateField(default = datetime.date.today, editable = False, db_index = True) + + def __unicode__(self): + if self.name: + return u'%s <%s>' % (self.name, self.email) + return self.email + + class Meta: + ordering = ('name', 'email') + + def setPending(self): + self.status = 'P' + self.status_date = datetime.date.today() + self.genKey() + + def setActive(self): + self.status = 'A' + self.status_date = datetime.date.today() + + def setLeaving(self): + self.status = 'L' + self.status_date = datetime.date.today() + self.genKey() + + def isPending(self): + return self.status == 'P' + + def isLeaving(self): + return self.status == 'L' + + def isActive(self): + return self.status == 'A' + + def current_status(self): + if self.status == 'P': + return 'Pending' + elif self.status == 'L': + return 'Leaving' + elif self.status == 'A': + return 'Active' + else: + return 'Unknown' + + def genKey(self): + self.key = ''.join(random.sample(string.ascii_letters + string.digits, self.keyLength)) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/band/urls.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,28 @@ +from django.conf.urls.defaults import * + +urlpatterns = patterns('band.views', + (r'^$', 'index'), + (r'^bio/$', 'bio'), + (r'^buy/$', 'buy'), + (r'^contact/$', 'contact'), + (r'^gigs/$', 'gigs'), + (r'^gigs/flyers$', 'flyers'), + (r'^mail/$', 'mail'), + (r'^mail/confirm/([a-zA-Z0-9]+)$', 'mail_confirm'), + (r'^mail/not_found$', 'mail_not_found'), + (r'^mail/thanks$', 'mail_thanks'), + (r'^mail/unsubscribe$', 'mail_unsubscribe'), + (r'^news/$', 'news'), + (r'^photos/$', 'photos_index'), + (r'^photos/(\d+)$', 'photo_detail'), + (r'^press/$', 'press_index'), + (r'^press/(\d+)$', 'press_detail'), + (r'^songs/$', 'songs'), + (r'^videos/$', 'videos_index'), + (r'^videos/(\d+)$', 'video_detail'), +) + +urlpatterns += patterns('band.admin_views', + (r'^admin/band/email/$', 'email'), + (r'^admin/band/email_sent/$', 'email_sent'), +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/band/views.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,340 @@ +####################################################################### +# +# PyBand Copyright (C) 2008 - 2011 by Brian Neal +# +####################################################################### +import collections +import datetime +import random + +from django import forms +from django.core.urlresolvers import reverse +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response +from django.shortcuts import get_object_or_404 +from django.template import RequestContext +from django.template.loader import render_to_string +from django.core.mail import send_mail +from django.db import connection + +from band.models import Article +from band.models import Album +from band.models import Band +from band.models import Fan +from band.models import Gear +from band.models import Gig +from band.models import Member +from band.models import Merchandise +from band.models import Mp3 +from band.models import Mp3_Set +from band.models import News +from band.models import SiteConfig +from band.models import Video_Set +from photologue.models import Gallery +from photologue.models import Photo + +####################################################################### + +def index(request): + config = SiteConfig.objects.get(pk = 1) + carpe = Photo.objects.get(title_slug = 'carpe-noctem') + sandstorm = Photo.objects.get(title_slug = 'sandstorm-cover') + ruins = Photo.objects.get(title_slug = 'ruins-cover') + + upcomingDates = Gig.objects.filter(date__gte = datetime.date.today).order_by('date')[:5] + + return render_to_response('band/index.html', + { + 'config' : config, + 'carpe' : carpe, + 'sandstorm' : sandstorm, + 'ruins' : ruins, + 'upcomingDates' : upcomingDates, + # 'tourPhotos' : tourPhotos, + }, + context_instance = RequestContext(request)) + +####################################################################### + +def bio(request): + members = Member.objects.exclude(is_active__exact = 0) + + return render_to_response('band/bio.html', + { 'members' : members, }, + context_instance = RequestContext(request)) + +####################################################################### + +def gigs(request): + today = datetime.date.today() + gigs = Gig.objects.select_related('venue', 'flyer', 'venue__city', + 'venue__city__state', 'venue__city__country') + upcoming = [] + previous = [] + + # To avoid many, many database hits in the template, we get all the + # bands out at once. We also get the many-to-many intermediate table + # that Django generated for us so we can associate bands to gigs. + # Since we don't know about this table we drop into raw SQL to get + # the contents. + + bands = dict((band.id, band) for band in Band.objects.all()) + cursor = connection.cursor() + cursor.execute('SELECT * FROM band_gig_bands') + gig_bands = collections.defaultdict(list) + for row in cursor.fetchall(): + gig_bands[row[1]].append(bands[row[2]]) + + for gig in gigs: + gig.bands_ = gig_bands[gig.id] + if gig.date >= today: + upcoming.append(gig) + else: + previous.append(gig) + + upcoming.reverse() + + stats = {} + venues = set() + cities = set() + states = set() + countries = set() + for gig in previous: + venues.add(gig.venue.id) + cities.add(gig.venue.city.id) + if gig.venue.city.state: + states.add(gig.venue.city.state.id) + if gig.venue.city.country: + countries.add(gig.venue.city.country.id) + + stats['count'] = len(previous) + stats['venues'] = len(venues) + stats['cities'] = len(cities) + stats['states'] = len(states) + stats['countries'] = len(countries) + stats['bands'] = len(bands) + + flyerGigs = Gig.objects.exclude(flyer__isnull = True).select_related( + 'venue', 'flyer').order_by('-date') + + return render_to_response('band/gigs.html', { + 'upcoming' : upcoming, + 'previous' : previous, + 'stats' : stats, + 'flyerGigs' : flyerGigs, + }, + context_instance = RequestContext(request)) + +####################################################################### + +def news(request): + news = News.objects.order_by('-date') + + return render_to_response('band/news.html', + { + 'news' : news + }, + context_instance = RequestContext(request)) + +####################################################################### + +def press_index(request): + articles = Article.objects.order_by('-date') + + return render_to_response('band/press.html', + { + 'articles' : articles + }, + context_instance = RequestContext(request)) + +####################################################################### + +def press_detail(request, id): + article = get_object_or_404(Article, pk = id) + + return render_to_response('band/press_detail.html', + { 'article' : article }, + context_instance = RequestContext(request)) + +####################################################################### + +def songs(request): + mp3Sets = Mp3_Set.objects.order_by('-date', '-id') + + return render_to_response('band/songs.html', + { 'mp3Sets' : mp3Sets }, + context_instance = RequestContext(request)) + +####################################################################### + +def photos_index(request): + galleries = Gallery.objects.values('title', 'id').order_by('-id') + + photos = Photo.objects.filter(is_public__exact = 1) + randomPhotos = random.sample(photos, 4) + + return render_to_response('band/photos.html', + { 'galleries' : galleries, 'randomPhotos' : randomPhotos }, + context_instance = RequestContext(request)) + +####################################################################### + +def photo_detail(request, id): + gallery = get_object_or_404(Gallery, pk = id) + photos = gallery.photos.order_by('id') + return render_to_response('band/photo_detail.html', + {'gallery' : gallery, 'photos': photos }, + context_instance = RequestContext(request)) + +####################################################################### + +def videos_index(request): + vidsets = Video_Set.objects.values('title', 'id').order_by('-date') + return render_to_response('band/videos.html', + { 'vidsets' : vidsets }, + context_instance = RequestContext(request)) + +####################################################################### + +def video_detail(request, id): + vidset = get_object_or_404(Video_Set, pk = id) + + return render_to_response('band/video_detail.html', + { 'vidset' : vidset }, + context_instance = RequestContext(request)) + +####################################################################### + +def buy(request): + albums = Album.objects.all().order_by('-id') + merchandise = Merchandise.objects.all().order_by('-id') + config = SiteConfig.objects.values('ordering_info').get(pk = 1) + return render_to_response('band/buy.html', + { 'albums' : albums, 'merchandise' : merchandise, 'config' : config }, + context_instance = RequestContext(request)) + +####################################################################### + +def confirmEmail(config, to, subscribe, key): + band = config.band_name + fromEmail = config.contact_email + url = config.url + if url[-1] != '/': + url += '/' + url += 'mail/confirm/' + key + + if subscribe: + emailTemplate = 'band/email_subscribe.txt' + else: + emailTemplate = 'band/email_unsubscribe.txt' + + msg = render_to_string(emailTemplate, { 'band' : band, 'url' : url, 'band_url' : config.url }) + + subject = '[' + band + '] Mailing List Confirmation' + + send_mail(subject, msg, fromEmail, [to]) + +####################################################################### + +def contact(request): + config = SiteConfig.objects.get(pk = 1) + band = Member.objects.exclude(is_active__exact = 0).order_by('order') + return render_to_response('band/contact.html', + { 'config' : config, 'band' : band }, + context_instance = RequestContext(request)) + +####################################################################### + +class ContactForm(forms.Form): + name = forms.CharField(max_length = 32, required = False, + widget = forms.TextInput(attrs = {'class' : 'form-box'})) + email = forms.EmailField(widget = forms.TextInput(attrs = {'class' : 'form-box'})) + location = forms.CharField(max_length = 32, required = False, + widget = forms.TextInput(attrs = {'class' : 'form-box'})) + option = forms.ChoiceField(choices = (('subscribe', 'Subscribe'), ('unsubscribe', 'Unsubscribe')), + widget = forms.Select(attrs = {'class' : 'form-box'})) + +def mail(request): + config = SiteConfig.objects.get(pk = 1) + form = ContactForm() + if request.method == 'POST': + form = ContactForm(request.POST) + if form.is_valid(): + if form.cleaned_data['option'] == 'unsubscribe': + try: + fan = Fan.objects.get(email = form.cleaned_data['email']) + except Fan.DoesNotExist: + return HttpResponseRedirect(reverse(mail_not_found)) + + fan.setLeaving() + fan.save() + confirmEmail(config, fan.email, False, fan.key) + return HttpResponseRedirect(reverse(mail_unsubscribe)) + + elif form.cleaned_data['option'] == 'subscribe': + try: + fan = Fan.objects.get(email = form.cleaned_data['email']) + except Fan.DoesNotExist: + fan = Fan(name = form.cleaned_data['name'], + email = form.cleaned_data['email'], + location = form.cleaned_data['location']) + + fan.setPending() + fan.save() + confirmEmail(config, fan.email, True, fan.key) + return HttpResponseRedirect(reverse(mail_thanks)) + + return render_to_response('band/mail.html', + { 'form' : form }, + context_instance = RequestContext(request)) + +####################################################################### + +def mail_not_found(request): + return render_to_response('band/mail_not_found.html', + {}, + context_instance = RequestContext(request)) + +####################################################################### + +def mail_thanks(request): + return render_to_response('band/mail_thanks.html', + {}, + context_instance = RequestContext(request)) + +####################################################################### + +def mail_unsubscribe(request): + return render_to_response('band/mail_unsubscribe.html', + {}, + context_instance = RequestContext(request)) + +####################################################################### + +def mail_confirm(request, key): + fan = get_object_or_404(Fan, key = key) + + email = fan.email + action = 'subscribed' + + if fan.isPending(): + fan.setActive() + fan.save() + elif fan.isLeaving(): + fan.delete() + action = 'unsubscribed' + + return render_to_response('band/mail_confirm.html', + { 'email' : email, 'action' : action }, + context_instance = RequestContext(request)) + +####################################################################### + +def flyers(request): + + gigs = Gig.objects.exclude(flyer__isnull = True).order_by('-date') + + return render_to_response('band/flyers.html', + { 'gigs' : gigs }, + context_instance = RequestContext(request))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/manage.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/LICENSE.txt Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,27 @@ +Copyright (c) 2007-2008, Justin C. Driscoll +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of django-photologue nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/README.txt Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,38 @@ +Installation + +Step 1 - Download Photologue + +Photologue can be downloaded below or from the project page. Older versions are also available from the project page and users who like to live on the edge can checkout a copy of the latest trunk revision. + +Step 2 - Add Photologue To Your Project + +Copy the entire Photologue application folder (the folder named 'photologue' that contains 'models.py') to a location on your Python path such as your project root. Your project root is typically the directory where your 'settings.py' is found. + +Step 3 - Configure Your Settings + +Add 'photologue' to your INSTALLED_APPS setting: + + INSTALLED_APPS = ( + # ...other installed applications, + 'photologue', + ) + +Confirm that your MEDIA_ROOT and MEDIA_URL settings are correct. + +If you want to tweak things even more you can also over-ride a few default settings (optional, see documentation for more information on the available settings). + +Step 4 - Register Photologue with the Django Admin + +Add the following to your projects urls.py file: + + from django.contrib import admin + + admin.autodiscover() + +Step 4 - Sync Your Database + +Run the 'manage.py syndb' command to create the appropriate tables. After the database in initialized, Photologue will walk you through creating some default models. + +Additional documentation available here: + +http://code.google.com/p/django-photologue/w/list
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/admin.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,60 @@ +""" Newforms Admin configuration for Photologue + +""" +from django.contrib import admin +from models import * + +class GalleryAdmin(admin.ModelAdmin): + list_display = ('title', 'date_added', 'photo_count', 'is_public') + list_filter = ['date_added', 'is_public'] + date_hierarchy = 'date_added' + prepopulated_fields = {'title_slug': ('title',)} + filter_horizontal = ('photos',) + +class PhotoAdmin(admin.ModelAdmin): + list_display = ('title', 'date_taken', 'date_added', 'is_public', 'tags', 'view_count', 'admin_thumbnail') + list_filter = ['date_added', 'is_public'] + list_per_page = 10 + prepopulated_fields = {'title_slug': ('title',)} + +class PhotoEffectAdmin(admin.ModelAdmin): + list_display = ('name', 'description', 'admin_sample') + fieldsets = ( + (None, { + 'fields': ('name', 'description') + }), + ('Adjustments', { + 'fields': ('color', 'brightness', 'contrast', 'sharpness') + }), + ('Filters', { + 'fields': ('filters',) + }), + ('Reflection', { + 'fields': ('reflection_size', 'reflection_strength', 'background_color') + }), + ) + +class PhotoSizeAdmin(admin.ModelAdmin): + list_display = ('name', 'width', 'height', 'crop', 'pre_cache', 'effect', 'increment_count') + fieldsets = ( + (None, { + 'fields': ('name', 'width', 'height', 'quality') + }), + ('Options', { + 'fields': ('upscale', 'crop', 'pre_cache', 'increment_count') + }), + ('Enhancements', { + 'fields': ('effect', 'watermark',) + }), + ) + +class WatermarkAdmin(admin.ModelAdmin): + list_display = ('name', 'opacity', 'style') + + +admin.site.register(Gallery, GalleryAdmin) +admin.site.register(GalleryUpload) +admin.site.register(Photo, PhotoAdmin) +admin.site.register(PhotoEffect, PhotoEffectAdmin) +admin.site.register(PhotoSize, PhotoSizeAdmin) +admin.site.register(Watermark, WatermarkAdmin) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/locale/pl/LC_MESSAGES/django.po Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,419 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Photologue Preview 2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2008-07-22 23:05+0200\n" +"PO-Revision-Date: 2008-07-22 23:08+0100\n" +"Last-Translator: Jakub Wiśniowski <restless.being@gmail.com>\n" +"Language-Team: Jakub Wiśniowski <restless.being@gmail.com>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n\n" +"X-Poedit-Language: Polish\n" +"X-Poedit-Country: POLAND\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: models.py:32 +msgid "Photologue was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path." +msgstr "Photologue nie był w stanie zaimportować Python Imaging Library. Upewnij się, że pakiet ten jest zainstalowany i znajduje się w ścieżce dostępnej dla Pythona." + +#: models.py:38 +msgid "Separate tags with spaces, put quotes around multiple-word tags." +msgstr "Rozdziel tagi spacjami, ujmij w cudzysłowy tagi złożone z wielu słów." + +#: models.py:47 +msgid "Django-tagging was not found, tags will be treated as plain text." +msgstr "Django-tagging nie zostało znalezione. Tagi będą traktowane jako czysty tekst." + +#: models.py:64 +msgid "Very Low" +msgstr "Bardzo niska" + +#: models.py:65 +msgid "Low" +msgstr "Niska" + +#: models.py:66 +msgid "Medium-Low" +msgstr "Niższa średnia" + +#: models.py:67 +msgid "Medium" +msgstr "Średnia" + +#: models.py:68 +msgid "Medium-High" +msgstr "Wyższa średnia" + +#: models.py:69 +msgid "High" +msgstr "Wysoka" + +#: models.py:70 +msgid "Very High" +msgstr "Bardzo wysoka" + +#: models.py:75 +msgid "Top" +msgstr "Góra" + +#: models.py:76 +msgid "Right" +msgstr "Prawo" + +#: models.py:77 +msgid "Bottom" +msgstr "Dół" + +#: models.py:78 +msgid "Left" +msgstr "Lewo" + +#: models.py:79 +msgid "Center (Default)" +msgstr "Środek (Domyślnie)" + +#: models.py:83 +msgid "Flip left to right" +msgstr "Odbij w poziomie" + +#: models.py:84 +msgid "Flip top to bottom" +msgstr "Odbij w pionie" + +#: models.py:85 +msgid "Rotate 90 degrees counter-clockwise" +msgstr "Odwróć 90 stopni w lewo" + +#: models.py:86 +msgid "Rotate 90 degrees clockwise" +msgstr "Odwróć 90 stopni w prawo" + +#: models.py:87 +msgid "Rotate 180 degrees" +msgstr "Obróć o 180 stopni" + +#: models.py:91 +msgid "Tile" +msgstr "Kafelki" + +#: models.py:92 +msgid "Scale" +msgstr "Skaluj" + +#: models.py:102 +#, python-format +msgid "Chain multiple filters using the following pattern \"FILTER_ONE->FILTER_TWO->FILTER_THREE\". Image filters will be applied in order. The following filter are available: %s." +msgstr "Połącz wiele filtrów używając następującego wzorca: \"FILTR_PIERWSZY->FILTR_DRUGI->FILTR_TRZECI\". Filtry obrazów będą zastosowane w kolejności. Dostępne są następujące filtry: %s." + +#: models.py:107 +msgid "date published" +msgstr "data publikacji" + +#: models.py:108 +#: models.py:164 +#: models.py:448 +msgid "title" +msgstr "tytuł" + +#: models.py:109 +msgid "title slug" +msgstr "tytuł - slug " + +#: models.py:110 +msgid "A \"slug\" is a unique URL-friendly title for an object." +msgstr "\"Slug\" jest unikalnym, zgodnym z formatem dla URL-i tytułem obiektu." + +#: models.py:111 +#: models.py:166 +#: models.py:483 +msgid "description" +msgstr "opis" + +#: models.py:112 +#: models.py:167 +#: models.py:453 +msgid "is public" +msgstr "jest publiczna" + +#: models.py:113 +msgid "Public galleries will be displayed in the default views." +msgstr "Galerie publiczne będą wyświetlana w domyślnych widokach." + +#: models.py:114 +#: models.py:460 +msgid "photos" +msgstr "zdjęcia" + +#: models.py:116 +#: models.py:168 +#: models.py:454 +msgid "tags" +msgstr "tagi" + +#: models.py:121 +msgid "gallery" +msgstr "galeria" + +#: models.py:122 +msgid "galleries" +msgstr "galerie" + +#: models.py:155 +msgid "count" +msgstr "ilość" + +#: models.py:162 +msgid "images file (.zip)" +msgstr "plik z obrazami (.zip)" + +#: models.py:163 +msgid "Select a .zip file of images to upload into a new Gallery." +msgstr "Wybierz plik .zip zawierający zdjęcia które chcesz załadować do nowej Galerii." + +#: models.py:164 +msgid "All photos in the gallery will be given a title made up of the gallery title + a sequential number." +msgstr "Wszystkie " + +#: models.py:165 +#: models.py:451 +msgid "caption" +msgstr "podpis" + +#: models.py:165 +msgid "Caption will be added to all photos." +msgstr "Podpis będzie dodany do wszystkich zdjęć." + +#: models.py:166 +msgid "A description of this Gallery." +msgstr "Opis tej Galerii." + +#: models.py:167 +msgid "Uncheck this to make the uploaded gallery and included photographs private." +msgstr "Odznacz aby uczynić wrzucaną galerię oraz zawarte w niej zdjęcia prywatnymi." + +#: models.py:171 +msgid "gallery upload" +msgstr "wrzucona galeria" + +#: models.py:172 +msgid "gallery uploads" +msgstr "wrzucone galerie" + +#: models.py:228 +#: models.py:594 +msgid "image" +msgstr "obraz" + +#: models.py:229 +msgid "date taken" +msgstr "data wykonania" + +#: models.py:231 +msgid "crop from" +msgstr "obetnij z" + +#: models.py:232 +msgid "effect" +msgstr "efekt" + +#: models.py:250 +msgid "An \"admin_thumbnail\" photo size has not been defined." +msgstr "Rozmiar zdjęcia \"admin_thumbnail\" nie został zdefiniowany." + +#: models.py:258 +msgid "Thumbnail" +msgstr "Miniaturka" + +#: models.py:449 +msgid "slug" +msgstr "slug" + +#: models.py:452 +msgid "date added" +msgstr "data dodania" + +#: models.py:453 +msgid "Public photographs will be displayed in the default views." +msgstr "Publiczne zdjęcia będą wyświetlane w domyślnych widokach." + +#: models.py:459 +msgid "photo" +msgstr "zdjęcie" + +#: models.py:482 +#: models.py:608 +msgid "name" +msgstr "nazwa" + +#: models.py:554 +msgid "rotate or flip" +msgstr "obróć lub odbij" + +#: models.py:555 +#: models.py:562 +msgid "color" +msgstr "kolor" + +#: models.py:555 +msgid "A factor of 0.0 gives a black and white image, a factor of 1.0 gives the original image." +msgstr "Współczynnik 0.0 daje czarno-biały obraz, współczynnik 1.0 daje obraz oryginalny." + +#: models.py:556 +msgid "brightness" +msgstr "jasność" + +#: models.py:556 +msgid "A factor of 0.0 gives a black image, a factor of 1.0 gives the original image." +msgstr "Współczynnik 0.0 daje czarny obraz, współczynnik 1.0 daje obraz oryginalny." + +#: models.py:557 +msgid "contrast" +msgstr "kontrast" + +#: models.py:557 +msgid "A factor of 0.0 gives a solid grey image, a factor of 1.0 gives the original image." +msgstr "Współczynnik 0.0 daje jednolity szary obraz, współczynnik 1.0 daje obraz oryginalny." + +#: models.py:558 +msgid "sharpness" +msgstr "ostrość" + +#: models.py:558 +msgid "A factor of 0.0 gives a blurred image, a factor of 1.0 gives the original image." +msgstr "Współczynnik 0.0 daje rozmazany obraz, współczynnik 1.0 daje obraz oryginalny." + +#: models.py:559 +msgid "filters" +msgstr "filtry" + +#: models.py:560 +msgid "size" +msgstr "rozmiar" + +#: models.py:560 +msgid "The height of the reflection as a percentage of the orignal image. A factor of 0.0 adds no reflection, a factor of 1.0 adds a reflection equal to the height of the orignal image." +msgstr "Wysokość odbicia jako procent oryginalnego obrazu. Współczynnik 0.0 nie dodaje odbicia, współczynnik 1.0 dodaje odbicie równe wysokości oryginalnego obrazu." + +#: models.py:561 +msgid "strength" +msgstr "intensywność" + +#: models.py:565 +#: models.py:616 +msgid "photo effect" +msgstr "efekt zdjęcia" + +#: models.py:566 +msgid "photo effects" +msgstr "efekty zdjęć" + +#: models.py:595 +msgid "style" +msgstr "styl" + +#: models.py:596 +msgid "opacity" +msgstr "przeźroczystość" + +#: models.py:596 +msgid "The opacity of the overlay." +msgstr "Poziom przezroczystości" + +#: models.py:599 +msgid "watermark" +msgstr "znak wodny" + +#: models.py:600 +msgid "watermarks" +msgstr "znaki wodne" + +#: models.py:608 +msgid "Photo size name should contain only letters, numbers and underscores. Examples: \"thumbnail\", \"display\", \"small\", \"main_page_widget\"." +msgstr "Nazwa rozmiaru zdjęcia powinna zawierać tylko litery, cyfry i podkreślenia. Przykłady: \"miniatura\", \"wystawa\", \"male\", \"widget_strony_glownej\"." + +#: models.py:609 +msgid "width" +msgstr "szerokość" + +#: models.py:609 +msgid "If width is set to \"0\" the image will be scaled to the supplied height." +msgstr "Jeśli szerokość jest ustawiona na \"0\" to obraz będzie skalowany do podanej wysokości." + +#: models.py:610 +msgid "height" +msgstr "wysokość" + +#: models.py:610 +msgid "If height is set to \"0\" the image will be scaled to the supplied width" +msgstr "Jeśli wysokość jest ustawiona na \"0\" to obraz będzie skalowany do podanej szerokości." + +#: models.py:611 +msgid "quality" +msgstr "jakość" + +#: models.py:611 +msgid "JPEG image quality." +msgstr "Jakość obrazu JPEG" + +#: models.py:612 +msgid "upscale images?" +msgstr "skalować obrazy w górę?" + +#: models.py:612 +msgid "If selected the image will be scaled up if necessary to fit the supplied dimensions. Cropped sizes will be upscaled regardless of this setting." +msgstr "Jeśli zaznaczone to obraz będzie skalowany w górę tak aby pasował do podanych wymiarów. Obcinane rozmiary będą skalowane niezależnie od tego ustawienia." + +#: models.py:613 +msgid "crop to fit?" +msgstr "przyciąć aby pasował?" + +#: models.py:613 +msgid "If selected the image will be scaled and cropped to fit the supplied dimensions." +msgstr "Jeśli zaznaczone to obraz będzie skalowany i przycinany tak aby pasował do podanych wymiarów." + +#: models.py:614 +msgid "pre-cache?" +msgstr "wstępnie cachować?" + +#: models.py:614 +msgid "If selected this photo size will be pre-cached as photos are added." +msgstr "Jesli zaznaczone to ten rozmiar zdjęć będzie wstępnie cachowany przy dodawaniu zdjęć." + +#: models.py:615 +msgid "increment view count?" +msgstr "zwiększyć licznik odsłon?" + +#: models.py:615 +msgid "If selected the image's \"view_count\" will be incremented when this photo size is displayed." +msgstr "Jeśli zaznaczone to \"licznik_odslon\" będzie zwiększany gdy ten rozmiar zdjęcia będzie wyświetlany." + +#: models.py:617 +msgid "watermark image" +msgstr "oznacz kluczem wodnym" + +#: models.py:621 +msgid "photo size" +msgstr "rozmiar zdjęcia" + +#: models.py:622 +msgid "photo sizes" +msgstr "rozmiary zdjęć" + +#: models.py:640 +msgid "A PhotoSize must have a positive height or width." +msgstr "PhotoSize musi mieć dodatnią wysokość i szerokość." + +#~ msgid "Leave to size the image to the set height" +#~ msgstr "Ustaw aby przeskalować obraz do wybranej wysokości" +#~ msgid "Leave to size the image to the set width" +#~ msgstr "Ustaw aby przeskalować obraz do wybranej szerokości" +#~ msgid "original image" +#~ msgstr "oryginalny obraz" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/management/__init__.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,1 @@ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/management/commands/__init__.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,37 @@ +from photologue.models import PhotoSize + +def get_response(msg, func=int, default=None): + while True: + resp = raw_input(msg) + if not resp and default is not None: + return default + try: + return func(resp) + except: + print 'Invalid input.' + +def create_photosize(name, width=0, height=0, crop=False, pre_cache=False, increment_count=False): + try: + size = PhotoSize.objects.get(name=name) + exists = True + except PhotoSize.DoesNotExist: + size = PhotoSize(name=name) + exists = False + if exists: + msg = 'A "%s" photo size already exists. Do you want to replace it? (yes, no):' % name + if not get_response(msg, lambda inp: inp == 'yes', False): + return + print '\nWe will now define the "%s" photo size:\n' % size + w = get_response('Width (in pixels):', lambda inp: int(inp), width) + h = get_response('Height (in pixels):', lambda inp: int(inp), height) + c = get_response('Crop to fit? (yes, no):', lambda inp: inp == 'yes', crop) + p = get_response('Pre-cache? (yes, no):', lambda inp: inp == 'yes', pre_cache) + i = get_response('Increment count? (yes, no):', lambda inp: inp == 'yes', increment_count) + size.width = w + size.height = h + size.crop = c + size.pre_cache = p + size.increment_count = i + size.save() + print '\nA "%s" photo size has been created.\n' % name + return size \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/management/commands/plcache.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,43 @@ +from django.core.management.base import BaseCommand, CommandError +from optparse import make_option +from photologue.models import PhotoSize, ImageModel + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--reset', '-r', action='store_true', dest='reset', help='Reset photo cache before generating'), + ) + + help = ('Manages Photologue cache file for the given sizes.') + args = '[sizes]' + + requires_model_validation = True + can_import_settings = True + + def handle(self, *args, **options): + return create_cache(args, options) + +def create_cache(sizes, options): + """ + Creates the cache for the given files + """ + reset = options.get('reset', None) + + size_list = [size.strip(' ,') for size in sizes] + + if len(size_list) < 1: + sizes = PhotoSize.objects.filter(pre_cache=True) + else: + sizes = PhotoSize.objects.filter(name__in=size_list) + + if not len(sizes): + raise CommandError('No photo sizes were found.') + + print 'Caching photos, this may take a while...' + + for cls in ImageModel.__subclasses__(): + for photosize in sizes: + print 'Cacheing %s size images' % photosize.name + for obj in cls.objects.all(): + if reset: + obj.remove_size(photosize) + obj.create_size(photosize)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/management/commands/plcreatesize.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,13 @@ +from django.core.management.base import BaseCommand, CommandError +from photologue.management.commands import create_photosize + +class Command(BaseCommand): + help = ('Creates a new Photologue photo size interactively.') + requires_model_validation = True + can_import_settings = True + + def handle(self, *args, **options): + create_size(args[0]) + +def create_size(size): + create_photosize(size) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/management/commands/plflush.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,35 @@ +from django.core.management.base import BaseCommand, CommandError +from optparse import make_option +from photologue.models import PhotoSize, ImageModel + +class Command(BaseCommand): + help = ('Clears the Photologue cache for the given sizes.') + args = '[sizes]' + + requires_model_validation = True + can_import_settings = True + + def handle(self, *args, **options): + return create_cache(args, options) + +def create_cache(sizes, options): + """ + Clears the cache for the given files + """ + size_list = [size.strip(' ,') for size in sizes] + + if len(size_list) < 1: + sizes = PhotoSize.objects.all() + else: + sizes = PhotoSize.objects.filter(name__in=size_list) + + if not len(sizes): + raise CommandError('No photo sizes were found.') + + print 'Flushing cache...' + + for cls in ImageModel.__subclasses__(): + for photosize in sizes: + print 'Flushing %s size images' % photosize.name + for obj in cls.objects.all(): + obj.remove_size(photosize)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/management/commands/plinit.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,30 @@ +from django.core.management.base import BaseCommand, CommandError +from photologue.management.commands import get_response, create_photosize +from photologue.models import PhotoEffect + +class Command(BaseCommand): + help = ('Prompts the user to set up the default photo sizes required by Photologue.') + requires_model_validation = True + can_import_settings = True + + def handle(self, *args, **kwargs): + return init(*args, **kwargs) + +def init(*args, **kwargs): + msg = '\nPhotologue requires a specific photo size to display thumbnail previews in the Django admin application.\nWould you like to generate this size now? (yes, no):' + if get_response(msg, lambda inp: inp == 'yes', False): + admin_thumbnail = create_photosize('admin_thumbnail', width=100, height=75, crop=True, pre_cache=True) + msg = 'Would you like to apply a sample enhancement effect to your admin thumbnails? (yes, no):' + if get_response(msg, lambda inp: inp == 'yes', False): + effect, created = PhotoEffect.objects.get_or_create(name='Enhance Thumbnail', description="Increases sharpness and contrast. Works well for smaller image sizes such as thumbnails.", contrast=1.2, sharpness=1.3) + admin_thumbnail.effect = effect + admin_thumbnail.save() + msg = '\nPhotologue comes with a set of templates for setting up a complete photo gallery. These templates require you to define both a "thumbnail" and "display" size.\nWould you like to define them now? (yes, no):' + if get_response(msg, lambda inp: inp == 'yes', False): + thumbnail = create_photosize('thumbnail', width=100, height=75) + display = create_photosize('display', width=400, increment_count=True) + msg = 'Would you like to apply a sample reflection effect to your display images? (yes, no):' + if get_response(msg, lambda inp: inp == 'yes', False): + effect, created = PhotoEffect.objects.get_or_create(name='Display Reflection', description="Generates a reflection with a white background", reflection_size=0.4) + display.effect = effect + display.save() \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/models.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,700 @@ +import os +import random +import shutil +import zipfile + +from datetime import datetime +from inspect import isclass + +from django.db import models +from django.db.models.signals import post_init +from django.conf import settings +from django.core.files.base import ContentFile +from django.core.urlresolvers import reverse +from django.template.defaultfilters import slugify +from django.utils.functional import curry +from django.utils.translation import ugettext_lazy as _ + +# Required PIL classes may or may not be available from the root namespace +# depending on the installation method used. +try: + import Image + import ImageFile + import ImageFilter + import ImageEnhance +except ImportError: + try: + from PIL import Image + from PIL import ImageFile + from PIL import ImageFilter + from PIL import ImageEnhance + except ImportError: + raise ImportError(_('Photologue was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.')) + +# attempt to load the django-tagging TagField from default location, +# otherwise we substitude a dummy TagField. +try: + from tagging.fields import TagField + tagfield_help_text = _('Separate tags with spaces, put quotes around multiple-word tags.') +except ImportError: + class TagField(models.CharField): + def __init__(self, **kwargs): + default_kwargs = {'max_length': 255, 'blank': True} + default_kwargs.update(kwargs) + super(TagField, self).__init__(**default_kwargs) + def get_internal_type(self): + return 'CharField' + tagfield_help_text = _('Django-tagging was not found, tags will be treated as plain text.') + +from utils import EXIF +from utils.reflection import add_reflection +from utils.watermark import apply_watermark + +# Path to sample image +SAMPLE_IMAGE_PATH = getattr(settings, 'SAMPLE_IMAGE_PATH', os.path.join(os.path.dirname(__file__), 'res', 'sample.jpg')) # os.path.join(settings.PROJECT_PATH, 'photologue', 'res', 'sample.jpg' + +# Modify image file buffer size. +ImageFile.MAXBLOCK = getattr(settings, 'PHOTOLOGUE_MAXBLOCK', 256 * 2 ** 10) + +# Photologue image path relative to media root +PHOTOLOGUE_DIR = getattr(settings, 'PHOTOLOGUE_DIR', 'photologue') + +# Look for user function to define file paths +PHOTOLOGUE_PATH = getattr(settings, 'PHOTOLOGUE_PATH', None) +if PHOTOLOGUE_PATH is not None: + if callable(PHOTOLOGUE_PATH): + get_storage_path = PHOTOLOGUE_PATH + else: + parts = PHOTOLOGUE_PATH.split('.') + module_name = '.'.join(parts[:-1]) + module = __import__(module_name) + get_storage_path = getattr(module, parts[-1]) +else: + def get_storage_path(instance, filename): + return os.path.join(PHOTOLOGUE_DIR, 'photos', filename) + +# Quality options for JPEG images +JPEG_QUALITY_CHOICES = ( + (30, _('Very Low')), + (40, _('Low')), + (50, _('Medium-Low')), + (60, _('Medium')), + (70, _('Medium-High')), + (80, _('High')), + (90, _('Very High')), +) + +# choices for new crop_anchor field in Photo +CROP_ANCHOR_CHOICES = ( + ('top', _('Top')), + ('right', _('Right')), + ('bottom', _('Bottom')), + ('left', _('Left')), + ('center', _('Center (Default)')), +) + +IMAGE_TRANSPOSE_CHOICES = ( + ('FLIP_LEFT_RIGHT', _('Flip left to right')), + ('FLIP_TOP_BOTTOM', _('Flip top to bottom')), + ('ROTATE_90', _('Rotate 90 degrees counter-clockwise')), + ('ROTATE_270', _('Rotate 90 degrees clockwise')), + ('ROTATE_180', _('Rotate 180 degrees')), +) + +WATERMARK_STYLE_CHOICES = ( + ('tile', _('Tile')), + ('scale', _('Scale')), +) + +# Prepare a list of image filters +filter_names = [] +for n in dir(ImageFilter): + klass = getattr(ImageFilter, n) + if isclass(klass) and issubclass(klass, ImageFilter.BuiltinFilter) and \ + hasattr(klass, 'name'): + filter_names.append(klass.__name__) +IMAGE_FILTERS_HELP_TEXT = _('Chain multiple filters using the following pattern "FILTER_ONE->FILTER_TWO->FILTER_THREE". Image filters will be applied in order. The following filters are available: %s.' % (', '.join(filter_names))) + + +class Gallery(models.Model): + date_added = models.DateTimeField(_('date published'), default=datetime.now) + title = models.CharField(_('title'), max_length=100, unique=True) + title_slug = models.SlugField(_('title slug'), unique=True, + help_text=_('A "slug" is a unique URL-friendly title for an object.')) + description = models.TextField(_('description'), blank=True) + is_public = models.BooleanField(_('is public'), default=True, + help_text=_('Public galleries will be displayed in the default views.')) + photos = models.ManyToManyField('Photo', related_name='galleries', verbose_name=_('photos'), + null=True, blank=True) + tags = TagField(help_text=tagfield_help_text, verbose_name=_('tags')) + + class Meta: + ordering = ['-date_added'] + get_latest_by = 'date_added' + verbose_name = _('gallery') + verbose_name_plural = _('galleries') + + def __unicode__(self): + return self.title + + def __str__(self): + return self.__unicode__() + + def get_absolute_url(self): + return reverse('pl-gallery', args=[self.title_slug]) + + def latest(self, limit=0, public=True): + if limit == 0: + limit = self.photo_count() + if public: + return self.public()[:limit] + else: + return self.photos.all()[:limit] + + def sample(self, count=0, public=True): + if count == 0 or count > self.photo_count(): + count = self.photo_count() + if public: + photo_set = self.public() + else: + photo_set = self.photos.all() + return random.sample(photo_set, count) + + def photo_count(self, public=True): + if public: + return self.public().count() + else: + return self.photos.all().count() + photo_count.short_description = _('count') + + def public(self): + return self.photos.filter(is_public=True) + + +class GalleryUpload(models.Model): + zip_file = models.FileField(_('images file (.zip)'), upload_to=PHOTOLOGUE_DIR+"/temp", + help_text=_('Select a .zip file of images to upload into a new Gallery.')) + gallery = models.ForeignKey(Gallery, null=True, blank=True, help_text=_('Select a gallery to add these images to. leave this empty to create a new gallery from the supplied title.')) + title = models.CharField(_('title'), max_length=75, help_text=_('All photos in the gallery will be given a title made up of the gallery title + a sequential number.')) + caption = models.TextField(_('caption'), blank=True, help_text=_('Caption will be added to all photos.')) + description = models.TextField(_('description'), blank=True, help_text=_('A description of this Gallery.')) + is_public = models.BooleanField(_('is public'), default=True, help_text=_('Uncheck this to make the uploaded gallery and included photographs private.')) + tags = models.CharField(max_length=255, blank=True, help_text=tagfield_help_text, verbose_name=_('tags')) + + class Meta: + verbose_name = _('gallery upload') + verbose_name_plural = _('gallery uploads') + + def save(self): + super(GalleryUpload, self).save() + self.process_zipfile() + super(GalleryUpload, self).delete() + + def process_zipfile(self): + if os.path.isfile(self.zip_file.path): + # TODO: implement try-except here + zip = zipfile.ZipFile(self.zip_file.path) + bad_file = zip.testzip() + if bad_file: + raise Exception('"%s" in the .zip archive is corrupt.' % bad_file) + count = 1 + if self.gallery: + gallery = self.gallery + else: + gallery = Gallery.objects.create(title=self.title, + title_slug=slugify(self.title), + description=self.description, + is_public=self.is_public, + tags=self.tags) + from cStringIO import StringIO + for filename in zip.namelist(): + if filename.startswith('__'): # do not process meta files + continue + data = zip.read(filename) + if len(data): + try: + # the following is taken from django.newforms.fields.ImageField: + # load() is the only method that can spot a truncated JPEG, + # but it cannot be called sanely after verify() + trial_image = Image.open(StringIO(data)) + trial_image.load() + # verify() is the only method that can spot a corrupt PNG, + # but it must be called immediately after the constructor + trial_image = Image.open(StringIO(data)) + trial_image.verify() + except Exception: + # if a "bad" file is found we just skip it. + continue + while 1: + title = ' '.join([self.title, str(count)]) + slug = slugify(title) + try: + p = Photo.objects.get(title_slug=slug) + except Photo.DoesNotExist: + photo = Photo(title=title, title_slug=slug, + caption=self.caption, + is_public=self.is_public, + tags=self.tags) + photo.image.save(filename, ContentFile(data)) + gallery.photos.add(photo) + count = count + 1 + break + count = count + 1 + zip.close() + + +class ImageModel(models.Model): + image = models.ImageField(_('image'), upload_to=get_storage_path) + date_taken = models.DateTimeField(_('date taken'), null=True, blank=True, editable=False) + view_count = models.PositiveIntegerField(default=0, editable=False) + crop_from = models.CharField(_('crop from'), blank=True, max_length=10, default='center', choices=CROP_ANCHOR_CHOICES) + effect = models.ForeignKey('PhotoEffect', null=True, blank=True, related_name="%(class)s_related", verbose_name=_('effect')) + + class Meta: + abstract = True + + @property + def EXIF(self): + try: + return EXIF.process_file(open(self.image.path, 'rb')) + except: + try: + return EXIF.process_file(open(self.image.path, 'rb'), details=False) + except: + return {} + + def admin_thumbnail(self): + func = getattr(self, 'get_admin_thumbnail_url', None) + if func is None: + return _('An "admin_thumbnail" photo size has not been defined.') + else: + if hasattr(self, 'get_absolute_url'): + return u'<a href="%s"><img src="%s"></a>' % \ + (self.get_absolute_url(), func()) + else: + return u'<a href="%s"><img src="%s"></a>' % \ + (self.image.url, func()) + admin_thumbnail.short_description = _('Thumbnail') + admin_thumbnail.allow_tags = True + + def cache_path(self): + return os.path.join(os.path.dirname(self.image.path), "cache") + + def cache_url(self): + return '/'.join([os.path.dirname(self.image.url), "cache"]) + + def image_filename(self): + return os.path.basename(self.image.path) + + def _get_filename_for_size(self, size): + size = getattr(size, 'name', size) + base, ext = os.path.splitext(self.image_filename()) + return ''.join([base, '_', size, ext]) + + def _get_SIZE_photosize(self, size): + return PhotoSizeCache().sizes.get(size) + + def _get_SIZE_size(self, size): + photosize = PhotoSizeCache().sizes.get(size) + if not self.size_exists(photosize): + self.create_size(photosize) + return Image.open(self._get_SIZE_filename(size)).size + + def _get_SIZE_url(self, size): + photosize = PhotoSizeCache().sizes.get(size) + if not self.size_exists(photosize): + self.create_size(photosize) + if photosize.increment_count: + self.view_count += 1 + self.save(update=True) + return '/'.join([self.cache_url(), self._get_filename_for_size(photosize.name)]) + + def _get_SIZE_filename(self, size): + photosize = PhotoSizeCache().sizes.get(size) + return os.path.join(self.cache_path(), + self._get_filename_for_size(photosize.name)) + + def add_accessor_methods(self, *args, **kwargs): + for size in PhotoSizeCache().sizes.keys(): + setattr(self, 'get_%s_size' % size, + curry(self._get_SIZE_size, size=size)) + setattr(self, 'get_%s_photosize' % size, + curry(self._get_SIZE_photosize, size=size)) + setattr(self, 'get_%s_url' % size, + curry(self._get_SIZE_url, size=size)) + setattr(self, 'get_%s_filename' % size, + curry(self._get_SIZE_filename, size=size)) + + def size_exists(self, photosize): + func = getattr(self, "get_%s_filename" % photosize.name, None) + if func is not None: + if os.path.isfile(func()): + return True + return False + + def resize_image(self, im, photosize): + cur_width, cur_height = im.size + new_width, new_height = photosize.size + if photosize.crop: + ratio = max(float(new_width)/cur_width,float(new_height)/cur_height) + x = (cur_width * ratio) + y = (cur_height * ratio) + xd = abs(new_width - x) + yd = abs(new_height - y) + x_diff = int(xd / 2) + y_diff = int(yd / 2) + if self.crop_from == 'top': + box = (int(x_diff), 0, int(x_diff+new_width), new_height) + elif self.crop_from == 'left': + box = (0, int(y_diff), new_width, int(y_diff+new_height)) + elif self.crop_from == 'bottom': + box = (int(x_diff), int(yd), int(x_diff+new_width), int(y)) # y - yd = new_height + elif self.crop_from == 'right': + box = (int(xd), int(y_diff), int(x), int(y_diff+new_height)) # x - xd = new_width + else: + box = (int(x_diff), int(y_diff), int(x_diff+new_width), int(y_diff+new_height)) + im = im.resize((int(x), int(y)), Image.ANTIALIAS).crop(box) + else: + if not new_width == 0 and not new_height == 0: + ratio = min(float(new_width)/cur_width, + float(new_height)/cur_height) + else: + if new_width == 0: + ratio = float(new_height)/cur_height + else: + ratio = float(new_width)/cur_width + new_dimensions = (int(round(cur_width*ratio)), + int(round(cur_height*ratio))) + if new_dimensions[0] > cur_width or \ + new_dimensions[1] > cur_height: + if not photosize.upscale: + return im + im = im.resize(new_dimensions, Image.ANTIALIAS) + return im + + def create_size(self, photosize): + if self.size_exists(photosize): + return + if not os.path.isdir(self.cache_path()): + os.makedirs(self.cache_path()) + try: + im = Image.open(self.image.path) + except IOError: + return + # Apply effect if found + if self.effect is not None: + im = self.effect.pre_process(im) + elif photosize.effect is not None: + im = photosize.effect.pre_process(im) + # Resize/crop image + if im.size != photosize.size: + im = self.resize_image(im, photosize) + # Apply watermark if found + if photosize.watermark is not None: + im = photosize.watermark.post_process(im) + # Apply effect if found + if self.effect is not None: + im = self.effect.post_process(im) + elif photosize.effect is not None: + im = photosize.effect.post_process(im) + # Save file + im_filename = getattr(self, "get_%s_filename" % photosize.name)() + try: + if im.format == 'JPEG': + im.save(im_filename, 'JPEG', quality=int(photosize.quality), optimize=True) + else: + im.save(im_filename) + except IOError, e: + if os.path.isfile(im_filename): + os.unlink(im_filename) + raise e + + def remove_size(self, photosize, remove_dirs=True): + if not self.size_exists(photosize): + return + filename = getattr(self, "get_%s_filename" % photosize.name)() + if os.path.isfile(filename): + os.remove(filename) + if remove_dirs: + self.remove_cache_dirs() + + def clear_cache(self): + cache = PhotoSizeCache() + for photosize in cache.sizes.values(): + self.remove_size(photosize, False) + self.remove_cache_dirs() + + def pre_cache(self): + cache = PhotoSizeCache() + for photosize in cache.sizes.values(): + if photosize.pre_cache: + self.create_size(photosize) + + def remove_cache_dirs(self): + try: + os.removedirs(self.cache_path()) + except: + pass + + def save(self, update=False): + if update: + models.Model.save(self) + return + if self.date_taken is None: + try: + exif_date = self.EXIF.get('EXIF DateTimeOriginal', None) + if exif_date is not None: + d, t = str.split(exif_date.values) + year, month, day = d.split(':') + hour, minute, second = t.split(':') + self.date_taken = datetime(int(year), int(month), int(day), + int(hour), int(minute), int(second)) + except: + pass + if self.date_taken is None: + self.date_taken = datetime.now() + if self._get_pk_val(): + self.clear_cache() + super(ImageModel, self).save() + self.pre_cache() + + def delete(self): + self.clear_cache() + super(ImageModel, self).delete() + + +class Photo(ImageModel): + title = models.CharField(_('title'), max_length=100, unique=True) + title_slug = models.SlugField(_('slug'), unique=True, + help_text=('A "slug" is a unique URL-friendly title for an object.')) + caption = models.TextField(_('caption'), blank=True) + date_added = models.DateTimeField(_('date added'), default=datetime.now, editable=False) + is_public = models.BooleanField(_('is public'), default=True, help_text=_('Public photographs will be displayed in the default views.')) + tags = TagField(help_text=tagfield_help_text, verbose_name=_('tags')) + + class Meta: + ordering = ['-date_added'] + get_latest_by = 'date_added' + verbose_name = _("photo") + verbose_name_plural = _("photos") + + def __unicode__(self): + return self.title + + def __str__(self): + return self.__unicode__() + + def save(self, update=False): + if self.title_slug is None: + self.title_slug = slugify(self.title) + super(Photo, self).save(update) + + def get_absolute_url(self): + return reverse('pl-photo', args=[self.title_slug]) + + def public_galleries(self): + """Return the public galleries to which this photo belongs.""" + return self.galleries.filter(is_public=True) + + +class BaseEffect(models.Model): + name = models.CharField(_('name'), max_length=30, unique=True) + description = models.TextField(_('description'), blank=True) + + class Meta: + abstract = True + + def sample_dir(self): + return os.path.join(settings.MEDIA_ROOT, PHOTOLOGUE_DIR, 'samples') + + def sample_url(self): + return settings.MEDIA_URL + '/'.join([PHOTOLOGUE_DIR, 'samples', '%s %s.jpg' % (self.name.lower(), 'sample')]) + + def sample_filename(self): + return os.path.join(self.sample_dir(), '%s %s.jpg' % (self.name.lower(), 'sample')) + + def create_sample(self): + if not os.path.isdir(self.sample_dir()): + os.makedirs(self.sample_dir()) + try: + im = Image.open(SAMPLE_IMAGE_PATH) + except IOError: + raise IOError('Photologue was unable to open the sample image: %s.' % SAMPLE_IMAGE_PATH) + im = self.process(im) + im.save(self.sample_filename(), 'JPEG', quality=90, optimize=True) + + def admin_sample(self): + return u'<img src="%s">' % self.sample_url() + admin_sample.short_description = 'Sample' + admin_sample.allow_tags = True + + def pre_process(self, im): + return im + + def post_process(self, im): + return im + + def process(self, im): + im = self.pre_process(im) + im = self.post_process(im) + return im + + def __unicode__(self): + return self.name + + def __str__(self): + return self.__unicode__() + + def save(self): + try: + os.remove(self.sample_filename()) + except: + pass + models.Model.save(self) + self.create_sample() + for size in self.photo_sizes.all(): + size.clear_cache() + # try to clear all related subclasses of ImageModel + for prop in [prop for prop in dir(self) if prop[-8:] == '_related']: + for obj in getattr(self, prop).all(): + obj.clear_cache() + obj.pre_cache() + + def delete(self): + try: + os.remove(self.sample_filename()) + except: + pass + super(PhotoEffect, self).delete() + + +class PhotoEffect(BaseEffect): + """ A pre-defined effect to apply to photos """ + transpose_method = models.CharField(_('rotate or flip'), max_length=15, blank=True, choices=IMAGE_TRANSPOSE_CHOICES) + color = models.FloatField(_('color'), default=1.0, help_text=_("A factor of 0.0 gives a black and white image, a factor of 1.0 gives the original image.")) + brightness = models.FloatField(_('brightness'), default=1.0, help_text=_("A factor of 0.0 gives a black image, a factor of 1.0 gives the original image.")) + contrast = models.FloatField(_('contrast'), default=1.0, help_text=_("A factor of 0.0 gives a solid grey image, a factor of 1.0 gives the original image.")) + sharpness = models.FloatField(_('sharpness'), default=1.0, help_text=_("A factor of 0.0 gives a blurred image, a factor of 1.0 gives the original image.")) + filters = models.CharField(_('filters'), max_length=200, blank=True, help_text=_(IMAGE_FILTERS_HELP_TEXT)) + reflection_size = models.FloatField(_('size'), default=0, help_text=_("The height of the reflection as a percentage of the orignal image. A factor of 0.0 adds no reflection, a factor of 1.0 adds a reflection equal to the height of the orignal image.")) + reflection_strength = models.FloatField(_('strength'), default=0.6, help_text="The initial opacity of the reflection gradient.") + background_color = models.CharField(_('color'), max_length=7, default="#FFFFFF", help_text="The background color of the reflection gradient. Set this to match the background color of your page.") + + class Meta: + verbose_name = _("photo effect") + verbose_name_plural = _("photo effects") + + def pre_process(self, im): + if self.transpose_method != '': + method = getattr(Image, self.transpose_method) + im = im.transpose(method) + if im.mode != 'RGB' and im.mode != 'RGBA': + return im + for name in ['Color', 'Brightness', 'Contrast', 'Sharpness']: + factor = getattr(self, name.lower()) + if factor != 1.0: + im = getattr(ImageEnhance, name)(im).enhance(factor) + for name in self.filters.split('->'): + image_filter = getattr(ImageFilter, name.upper(), None) + if image_filter is not None: + try: + im = im.filter(image_filter) + except ValueError: + pass + return im + + def post_process(self, im): + if self.reflection_size != 0.0: + im = add_reflection(im, bgcolor=self.background_color, amount=self.reflection_size, opacity=self.reflection_strength) + return im + + +class Watermark(BaseEffect): + image = models.ImageField(_('image'), upload_to=PHOTOLOGUE_DIR+"/watermarks") + style = models.CharField(_('style'), max_length=5, choices=WATERMARK_STYLE_CHOICES, default='scale') + opacity = models.FloatField(_('opacity'), default=1, help_text=_("The opacity of the overlay.")) + + class Meta: + verbose_name = _('watermark') + verbose_name_plural = _('watermarks') + + def post_process(self, im): + mark = Image.open(self.image.path) + return apply_watermark(im, mark, self.style, self.opacity) + + +class PhotoSize(models.Model): + name = models.CharField(_('name'), max_length=20, unique=True, help_text=_('Photo size name should contain only letters, numbers and underscores. Examples: "thumbnail", "display", "small", "main_page_widget".')) + width = models.PositiveIntegerField(_('width'), default=0, help_text=_('If width is set to "0" the image will be scaled to the supplied height.')) + height = models.PositiveIntegerField(_('height'), default=0, help_text=_('If height is set to "0" the image will be scaled to the supplied width')) + quality = models.PositiveIntegerField(_('quality'), choices=JPEG_QUALITY_CHOICES, default=70, help_text=_('JPEG image quality.')) + upscale = models.BooleanField(_('upscale images?'), default=False, help_text=_('If selected the image will be scaled up if necessary to fit the supplied dimensions. Cropped sizes will be upscaled regardless of this setting.')) + crop = models.BooleanField(_('crop to fit?'), default=False, help_text=_('If selected the image will be scaled and cropped to fit the supplied dimensions.')) + pre_cache = models.BooleanField(_('pre-cache?'), default=False, help_text=_('If selected this photo size will be pre-cached as photos are added.')) + increment_count = models.BooleanField(_('increment view count?'), default=False, help_text=_('If selected the image\'s "view_count" will be incremented when this photo size is displayed.')) + effect = models.ForeignKey('PhotoEffect', null=True, blank=True, related_name='photo_sizes', verbose_name=_('photo effect')) + watermark = models.ForeignKey('Watermark', null=True, blank=True, related_name='photo_sizes', verbose_name=_('watermark image')) + + class Meta: + ordering = ['width', 'height'] + verbose_name = _('photo size') + verbose_name_plural = _('photo sizes') + + def __unicode__(self): + return self.name + + def __str__(self): + return self.__unicode__() + + def clear_cache(self): + for cls in ImageModel.__subclasses__(): + for obj in cls.objects.all(): + obj.remove_size(self) + if self.pre_cache: + obj.create_size(self) + PhotoSizeCache().reset() + + def save(self): + if self.width + self.height <= 0: + raise ValueError(_('A PhotoSize must have a positive height or width.')) + super(PhotoSize, self).save() + PhotoSizeCache().reset() + self.clear_cache() + + def delete(self): + self.clear_cache() + super(PhotoSize, self).delete() + + def _get_size(self): + return (self.width, self.height) + def _set_size(self, value): + self.width, self.height = value + size = property(_get_size, _set_size) + + +class PhotoSizeCache(object): + __state = {"sizes": {}} + + def __init__(self): + self.__dict__ = self.__state + if not len(self.sizes): + sizes = PhotoSize.objects.all() + for size in sizes: + self.sizes[size.name] = size + + def reset(self): + self.sizes = {} + + +# Set up the accessor methods +def add_methods(sender, instance, signal, *args, **kwargs): + """ Adds methods to access sized images (urls, paths) + + after the Photo model's __init__ function completes, + this method calls "add_accessor_methods" on each instance. + """ + if hasattr(instance, 'add_accessor_methods'): + instance.add_accessor_methods() + +# connect the add_accessor_methods function to the post_init signal +post_init.connect(add_methods)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/gallery_archive.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,26 @@ +{% extends "photologue/root.html" %} + +{% block title %}Latest Photo Galleries{% endblock %} + +{% block content %} + +<h1>Latest Photo Galleries</h1> + +{% if latest %} + {% for gallery in latest %} + <div class="photo-gallery"> + <h2><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></h2> + {% for photo in gallery.sample|slice:sample_size %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} + </div> + {% endfor %} +{% else %} + <p>No galleries were found.</p> +{% endif %} + +<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/gallery_archive_day.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,26 @@ +{% extends "photologue/root.html" %} + +{% block title %}Galleries for {{ day|date }}{% endblock %} + +{% block content %} + +<h1>Galleries for {{ day|date }}</h1> + +{% if object_list %} + {% for gallery in object_list %} + <div class="photo-gallery"> + <h2>{{ gallery.title }}</h2> + {% for photo in gallery.sample|slice:sample_size %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} + </div> + {% endfor %} +{% else %} + <p>No galleries were found.</p> +{% endif %} + +<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/gallery_archive_month.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,26 @@ +{% extends "photologue/root.html" %} + +{% block title %}Galleries for {{ month|date:"F Y" }}{% endblock %} + +{% block content %} + +<h1>Galleries for {{ month|date:"F Y" }}</h1> + +{% if object_list %} + {% for gallery in object_list %} + <div class="photo-gallery"> + <h2>{{ gallery.title }}</h2> + {% for photo in gallery.sample|slice:sample_size %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} + </div> + {% endfor %} +{% else %} + <p>No galleries were found.</p> +{% endif %} + +<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/gallery_archive_year.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,16 @@ +{% extends "photologue/root.html" %} + +{% block title %}Galleries for {{ year }}{% endblock %} + +{% block content %} + +<h1>Galleries for {{ year }}</h1> +<ul> +{% for date in date_list %} +<li><a href="{{ date|date:"M"|lower }}/">{{ date|date:"F" }}</a></li> +{% endfor %} +</ul> + +<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/gallery_detail.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,19 @@ +{% extends "photologue/root.html" %} + +{% block title %}{{ object.title }}{% endblock %} + +{% block content %} + +<h1>{{ object.title }}</h1> +<h2>Originally published {{ object.date_added|date:"l, F jS, Y" }}</h2> +{% if object.description %}<p>{{ object.description }}</p>{% endif %} +<div class="photo-gallery"> + {% for photo in object.public %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} +</div> +<p><a href="{% url pl-gallery-list 1 %}">View all galleries</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/gallery_list.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,31 @@ +{% extends "photologue/root.html" %} + +{% block title %}All Galleries{% endblock %} + +{% block content %} + +<h1>All galleries</h1> + +{% if object_list %} + {% for gallery in object_list %} + <div class="photo-gallery"> + <h2><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></h2> + {% for photo in gallery.sample|slice:sample_size %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} + </div> + {% endfor %} +{% else %} + <p>No galleries were found.</p> +{% endif %} + +{% if is_paginated %} +<p>{{ hits }} galleries total.</p> +<div id="page_controls"> + <p>{% if has_previous %}<a href="{% url pl-gallery-list previous %}">Previous</a> | {% endif %} page {{ page }} of {{ pages }} {% if has_next %}| <a href="{% url pl-gallery-list next %}">Next</a>{% endif %}</p> +</div> +{% endif %} + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/photo_archive.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,20 @@ +{% extends "photologue/root.html" %} + +{% block title %}Latest Photos{% endblock %} + +{% block content %} + +<h1>Latest Photos</h1> + +{% if latest %} + {% for photo in latest %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} +{% else %} +<p>No photos were found.</p> +{% endif %} +<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/photo_archive_day.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,20 @@ +{% extends "photologue/root.html" %} + +{% block title %}Photos for {{ day|date }}{% endblock %} + +{% block content %} + +<h1>Photos for {{ day|date }}</h1> + +{% if object_list %} + {% for photo in object_list %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} +{% else %} +<p>No photos were found.</p> +{% endif %} +<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/photo_archive_month.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,20 @@ +{% extends "photologue/root.html" %} + +{% block title %}Photos for {{ month|date:"F Y" }}{% endblock %} + +{% block content %} + +<h1>Photos for {{ month|date:"F Y" }}</h1> + +{% if object_list %} + {% for photo in object_list %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} +{% else %} +<p>No photos were found.</p> +{% endif %} +<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/photo_archive_year.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,14 @@ +{% extends "photologue/root.html" %} + +{% block title %}Galleries for {{ year }}{% endblock %} + +{% block content %} + +<h1>Photos for {{ year }}</h1> +<ul> +{% for date in date_list %} +<li><a href="{{ date|date:"M"|lower }}/">{{ date|date:"F" }}</a></li> +{% endfor %} +</ul> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/photo_detail.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,21 @@ +{% extends "photologue/root.html" %} + +{% block title %}{{ object.title }}{% endblock %} + +{% block content %} + +<h1>{{ object.title }}</h1> +<div class="gallery-photo"> + <a href="{{ object.image.url }}"><img src="{{ object.get_display_url }}" alt="{{ object.title }}"/></a> + {% if object.caption %}<p>{{ object.caption }}</p>{% endif %} +</div> +{% if object.public_galleries %} +<h2>This photo is found in the following galleries:</h2> +<ol> +{% for gallery in object.public_galleries %} + <li><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></li> +{% endfor %} +</ol> +{% endif %} + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/photo_list.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,26 @@ +{% extends "photologue/root.html" %} + +{% block title %}All Photos{% endblock %} + +{% block content %} + +<h1>All Photos</h1> + +{% if object_list %} + {% for photo in object_list %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} +{% else %} +<p>No photos were found.</p> +{% endif %} + +{% if is_paginated %} +<p>{{ hits }} photos total.</p> +<div id="page_controls"> + <p>{% if has_previous %}<a href="{% url pl-photo-list previous %}">Previous</a> | {% endif %} page {{ page }} of {{ pages }} {% if has_next %}| <a href="{% url pl-photo-list next %}">Next</a>{% endif %}</p> +</div> +{% endif %} + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/templates/photologue/root.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,1 @@ +{% extends "base.html" %} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/tests.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,218 @@ +import os +import unittest +from django.conf import settings +from django.core.files.base import ContentFile +from django.test import TestCase + +from models import * + +# Path to sample image +RES_DIR = os.path.join(os.path.dirname(__file__), 'res') +LANDSCAPE_IMAGE_PATH = os.path.join(RES_DIR, 'test_landscape.jpg') +PORTRAIT_IMAGE_PATH = os.path.join(RES_DIR, 'test_portrait.jpg') +SQUARE_IMAGE_PATH = os.path.join(RES_DIR, 'test_square.jpg') + + +class TestPhoto(ImageModel): + """ Minimal ImageModel class for testing """ + name = models.CharField(max_length=30) + + +class PLTest(TestCase): + """ Base TestCase class """ + def setUp(self): + self.s = PhotoSize(name='test', width=100, height=100) + self.s.save() + self.pl = TestPhoto(name='landscape') + self.pl.image.save(os.path.basename(LANDSCAPE_IMAGE_PATH), + ContentFile(open(LANDSCAPE_IMAGE_PATH, 'rb').read())) + self.pl.save() + + def tearDown(self): + path = self.pl.image.path + self.pl.delete() + self.failIf(os.path.isfile(path)) + self.s.delete() + + +class PhotoTest(PLTest): + def test_new_photo(self): + self.assertEqual(TestPhoto.objects.count(), 1) + self.failUnless(os.path.isfile(self.pl.image.path)) + self.assertEqual(os.path.getsize(self.pl.image.path), + os.path.getsize(LANDSCAPE_IMAGE_PATH)) + + def test_exif(self): + self.assert_(len(self.pl.EXIF.keys()) > 0) + + def test_paths(self): + self.assertEqual(os.path.normpath(str(self.pl.cache_path())).lower(), + os.path.normpath(os.path.join(settings.MEDIA_ROOT, + PHOTOLOGUE_DIR, + 'photos', + 'cache')).lower()) + self.assertEqual(self.pl.cache_url(), + settings.MEDIA_URL + PHOTOLOGUE_DIR + '/photos/cache') + + def test_count(self): + for i in range(5): + self.pl.get_test_url() + self.assertEquals(self.pl.view_count, 0) + self.s.increment_count = True + self.s.save() + for i in range(5): + self.pl.get_test_url() + self.assertEquals(self.pl.view_count, 5) + + def test_precache(self): + # set the thumbnail photo size to pre-cache + self.s.pre_cache = True + self.s.save() + # make sure it created the file + self.failUnless(os.path.isfile(self.pl.get_test_filename())) + self.s.pre_cache = False + self.s.save() + # clear the cache and make sure the file's deleted + self.pl.clear_cache() + self.failIf(os.path.isfile(self.pl.get_test_filename())) + + def test_accessor_methods(self): + self.assertEquals(self.pl.get_test_photosize(), self.s) + self.assertEquals(self.pl.get_test_size(), + Image.open(self.pl.get_test_filename()).size) + self.assertEquals(self.pl.get_test_url(), + self.pl.cache_url() + '/' + \ + self.pl._get_filename_for_size(self.s)) + self.assertEquals(self.pl.get_test_filename(), + os.path.join(self.pl.cache_path(), + self.pl._get_filename_for_size(self.s))) + + +class ImageResizeTest(PLTest): + def setUp(self): + super(ImageResizeTest, self).setUp() + self.pp = TestPhoto(name='portrait') + self.pp.image.save(os.path.basename(PORTRAIT_IMAGE_PATH), + ContentFile(open(PORTRAIT_IMAGE_PATH, 'rb').read())) + self.pp.save() + self.ps = TestPhoto(name='square') + self.ps.image.save(os.path.basename(SQUARE_IMAGE_PATH), + ContentFile(open(SQUARE_IMAGE_PATH, 'rb').read())) + self.ps.save() + + def tearDown(self): + super(ImageResizeTest, self).tearDown() + self.pp.delete() + self.ps.delete() + + def test_resize_to_fit(self): + self.assertEquals(self.pl.get_test_size(), (100, 75)) + self.assertEquals(self.pp.get_test_size(), (75, 100)) + self.assertEquals(self.ps.get_test_size(), (100, 100)) + + def test_resize_to_fit_width(self): + self.s.size = (100, 0) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (100, 75)) + self.assertEquals(self.pp.get_test_size(), (100, 133)) + self.assertEquals(self.ps.get_test_size(), (100, 100)) + + def test_resize_to_fit_width_enlarge(self): + self.s.size = (2000, 0) + self.s.upscale = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), (2000, 1500)) + self.assertEquals(self.pp.get_test_size(), (2000, 2667)) + self.assertEquals(self.ps.get_test_size(), (2000, 2000)) + + def test_resize_to_fit_height(self): + self.s.size = (0, 100) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (133, 100)) + self.assertEquals(self.pp.get_test_size(), (75, 100)) + self.assertEquals(self.ps.get_test_size(), (100, 100)) + + def test_resize_to_fit_height_enlarge(self): + self.s.size = (0, 2000) + self.s.upscale = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), (2667, 2000)) + self.assertEquals(self.pp.get_test_size(), (1500, 2000)) + self.assertEquals(self.ps.get_test_size(), (2000, 2000)) + + def test_resize_and_crop(self): + self.s.crop = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), self.s.size) + self.assertEquals(self.pp.get_test_size(), self.s.size) + self.assertEquals(self.ps.get_test_size(), self.s.size) + + def test_resize_rounding_to_fit(self): + self.s.size = (113, 113) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (113, 85)) + self.assertEquals(self.pp.get_test_size(), (85, 113)) + self.assertEquals(self.ps.get_test_size(), (113, 113)) + + def test_resize_rounding_cropped(self): + self.s.size = (113, 113) + self.s.crop = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), self.s.size) + self.assertEquals(self.pp.get_test_size(), self.s.size) + self.assertEquals(self.ps.get_test_size(), self.s.size) + + def test_resize_one_dimension_width(self): + self.s.size = (1500, 1200) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (1500, 1125)) + + def test_resize_one_dimension_height(self): + self.s.size = (1600, 1100) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (1467, 1100)) + + def test_resize_no_upscale(self): + self.s.size = (2000, 2000) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (1600, 1200)) + + def test_resize_no_upscale_mixed_height(self): + self.s.size = (3200, 600) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (800, 600)) + + def test_resize_no_upscale_mixed_width(self): + self.s.size = (800, 2400) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (800, 600)) + + def test_resize_no_upscale_crop(self): + self.s.size = (2000, 2000) + self.s.crop = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), (2000, 2000)) + + def test_resize_upscale(self): + self.s.size = (2000, 2000) + self.s.upscale = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), (2000, 1500)) + self.assertEquals(self.pp.get_test_size(), (1500, 2000)) + self.assertEquals(self.ps.get_test_size(), (2000, 2000)) + + +class PhotoEffectTest(PLTest): + def test(self): + effect = PhotoEffect(name='test') + im = Image.open(self.pl.image.path) + self.assert_(isinstance(effect.pre_process(im), Image.Image)) + self.assert_(isinstance(effect.post_process(im), Image.Image)) + self.assert_(isinstance(effect.process(im), Image.Image)) + + +class PhotoSizeCacheTest(PLTest): + def test(self): + cache = PhotoSizeCache() + self.assertEqual(cache.sizes['test'], self.s) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/urls.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,36 @@ +from django.conf import settings +from django.conf.urls.defaults import * +from models import * + +# Number of random images from the gallery to display. +SAMPLE_SIZE = ":%s" % getattr(settings, 'GALLERY_SAMPLE_SIZE', 5) + +# galleries +gallery_args = {'date_field': 'date_added', 'allow_empty': True, 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}} +urlpatterns = patterns('django.views.generic.date_based', + url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'date_field': 'date_added', 'slug_field': 'title_slug', 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery-detail'), + url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', gallery_args, name='pl-gallery-archive-day'), + url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', gallery_args, name='pl-gallery-archive-month'), + url(r'^gallery/(?P<year>\d{4})/$', 'archive_year', gallery_args, name='pl-gallery-archive-year'), + url(r'^gallery/?$', 'archive_index', gallery_args, name='pl-gallery-archive'), +) +urlpatterns += patterns('django.views.generic.list_detail', + url(r'^gallery/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'slug_field': 'title_slug', 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery'), + url(r'^gallery/page/(?P<page>[0-9]+)/$', 'object_list', {'queryset': Gallery.objects.filter(is_public=True), 'allow_empty': True, 'paginate_by': 5, 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery-list'), +) + +# photographs +photo_args = {'date_field': 'date_added', 'allow_empty': True, 'queryset': Photo.objects.filter(is_public=True)} +urlpatterns += patterns('django.views.generic.date_based', + url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'date_field': 'date_added', 'slug_field': 'title_slug', 'queryset': Photo.objects.filter(is_public=True)}, name='pl-photo-detail'), + url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', photo_args, name='pl-photo-archive-day'), + url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', photo_args, name='pl-photo-archive-month'), + url(r'^photo/(?P<year>\d{4})/$', 'archive_year', photo_args, name='pl-photo-archive-year'), + url(r'^photo/$', 'archive_index', photo_args, name='pl-photo-archive'), +) +urlpatterns += patterns('django.views.generic.list_detail', + url(r'^photo/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'slug_field': 'title_slug', 'queryset': Photo.objects.filter(is_public=True)}, name='pl-photo'), + url(r'^photo/page/(?P<page>[0-9]+)/$', 'object_list', {'queryset': Photo.objects.filter(is_public=True), 'allow_empty': True, 'paginate_by': 20}, name='pl-photo-list'), +) + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/utils/EXIF.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,1568 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Library to extract EXIF information from digital camera image files +# http://sourceforge.net/projects/exif-py/ +# +# VERSION 1.0.7 +# +# To use this library call with: +# f = open(path_name, 'rb') +# tags = EXIF.process_file(f) +# +# To ignore makerNote tags, pass the -q or --quick +# command line arguments, or as +# f = open(path_name, 'rb') +# tags = EXIF.process_file(f, details=False) +# +# To stop processing after a certain tag is retrieved, +# pass the -t TAG or --stop-tag TAG argument, or as +# f = open(path_name, 'rb') +# tags = EXIF.process_file(f, stop_tag='TAG') +# +# where TAG is a valid tag name, ex 'DateTimeOriginal' +# +# These are useful when you are retrieving a large list of images +# +# Returned tags will be a dictionary mapping names of EXIF tags to their +# values in the file named by path_name. You can process the tags +# as you wish. In particular, you can iterate through all the tags with: +# for tag in tags.keys(): +# if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', +# 'EXIF MakerNote'): +# print "Key: %s, value %s" % (tag, tags[tag]) +# (This code uses the if statement to avoid printing out a few of the +# tags that tend to be long or boring.) +# +# The tags dictionary will include keys for all of the usual EXIF +# tags, and will also include keys for Makernotes used by some +# cameras, for which we have a good specification. +# +# Note that the dictionary keys are the IFD name followed by the +# tag name. For example: +# 'EXIF DateTimeOriginal', 'Image Orientation', 'MakerNote FocusMode' +# +# Copyright (c) 2002-2007 Gene Cash All rights reserved +# Copyright (c) 2007 Ianaré Sévi All rights reserved +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. Neither the name of the authors nor the names of its contributors +# may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# +# ----- See 'changes.txt' file for all contributors and changes ----- # +# + + +# Don't throw an exception when given an out of range character. +def make_string(seq): + str = "" + for c in seq: + # Screen out non-printing characters + if 32 <= c and c < 256: + str += chr(c) + # If no printing chars + if not str: + return seq + return str + +# Special version to deal with the code in the first 8 bytes of a user comment. +def make_string_uc(seq): + code = seq[0:8] + seq = seq[8:] + # Of course, this is only correct if ASCII, and the standard explicitly + # allows JIS and Unicode. + return make_string(seq) + +# field type descriptions as (length, abbreviation, full name) tuples +FIELD_TYPES = ( + (0, 'X', 'Proprietary'), # no such type + (1, 'B', 'Byte'), + (1, 'A', 'ASCII'), + (2, 'S', 'Short'), + (4, 'L', 'Long'), + (8, 'R', 'Ratio'), + (1, 'SB', 'Signed Byte'), + (1, 'U', 'Undefined'), + (2, 'SS', 'Signed Short'), + (4, 'SL', 'Signed Long'), + (8, 'SR', 'Signed Ratio'), + ) + +# dictionary of main EXIF tag names +# first element of tuple is tag name, optional second element is +# another dictionary giving names to values +EXIF_TAGS = { + 0x0100: ('ImageWidth', ), + 0x0101: ('ImageLength', ), + 0x0102: ('BitsPerSample', ), + 0x0103: ('Compression', + {1: 'Uncompressed TIFF', + 6: 'JPEG Compressed'}), + 0x0106: ('PhotometricInterpretation', ), + 0x010A: ('FillOrder', ), + 0x010D: ('DocumentName', ), + 0x010E: ('ImageDescription', ), + 0x010F: ('Make', ), + 0x0110: ('Model', ), + 0x0111: ('StripOffsets', ), + 0x0112: ('Orientation', + {1: 'Horizontal (normal)', + 2: 'Mirrored horizontal', + 3: 'Rotated 180', + 4: 'Mirrored vertical', + 5: 'Mirrored horizontal then rotated 90 CCW', + 6: 'Rotated 90 CW', + 7: 'Mirrored horizontal then rotated 90 CW', + 8: 'Rotated 90 CCW'}), + 0x0115: ('SamplesPerPixel', ), + 0x0116: ('RowsPerStrip', ), + 0x0117: ('StripByteCounts', ), + 0x011A: ('XResolution', ), + 0x011B: ('YResolution', ), + 0x011C: ('PlanarConfiguration', ), + 0x0128: ('ResolutionUnit', + {1: 'Not Absolute', + 2: 'Pixels/Inch', + 3: 'Pixels/Centimeter'}), + 0x012D: ('TransferFunction', ), + 0x0131: ('Software', ), + 0x0132: ('DateTime', ), + 0x013B: ('Artist', ), + 0x013E: ('WhitePoint', ), + 0x013F: ('PrimaryChromaticities', ), + 0x0156: ('TransferRange', ), + 0x0200: ('JPEGProc', ), + 0x0201: ('JPEGInterchangeFormat', ), + 0x0202: ('JPEGInterchangeFormatLength', ), + 0x0211: ('YCbCrCoefficients', ), + 0x0212: ('YCbCrSubSampling', ), + 0x0213: ('YCbCrPositioning', ), + 0x0214: ('ReferenceBlackWhite', ), + 0x828D: ('CFARepeatPatternDim', ), + 0x828E: ('CFAPattern', ), + 0x828F: ('BatteryLevel', ), + 0x8298: ('Copyright', ), + 0x829A: ('ExposureTime', ), + 0x829D: ('FNumber', ), + 0x83BB: ('IPTC/NAA', ), + 0x8769: ('ExifOffset', ), + 0x8773: ('InterColorProfile', ), + 0x8822: ('ExposureProgram', + {0: 'Unidentified', + 1: 'Manual', + 2: 'Program Normal', + 3: 'Aperture Priority', + 4: 'Shutter Priority', + 5: 'Program Creative', + 6: 'Program Action', + 7: 'Portrait Mode', + 8: 'Landscape Mode'}), + 0x8824: ('SpectralSensitivity', ), + 0x8825: ('GPSInfo', ), + 0x8827: ('ISOSpeedRatings', ), + 0x8828: ('OECF', ), + # print as string + 0x9000: ('ExifVersion', make_string), + 0x9003: ('DateTimeOriginal', ), + 0x9004: ('DateTimeDigitized', ), + 0x9101: ('ComponentsConfiguration', + {0: '', + 1: 'Y', + 2: 'Cb', + 3: 'Cr', + 4: 'Red', + 5: 'Green', + 6: 'Blue'}), + 0x9102: ('CompressedBitsPerPixel', ), + 0x9201: ('ShutterSpeedValue', ), + 0x9202: ('ApertureValue', ), + 0x9203: ('BrightnessValue', ), + 0x9204: ('ExposureBiasValue', ), + 0x9205: ('MaxApertureValue', ), + 0x9206: ('SubjectDistance', ), + 0x9207: ('MeteringMode', + {0: 'Unidentified', + 1: 'Average', + 2: 'CenterWeightedAverage', + 3: 'Spot', + 4: 'MultiSpot'}), + 0x9208: ('LightSource', + {0: 'Unknown', + 1: 'Daylight', + 2: 'Fluorescent', + 3: 'Tungsten', + 10: 'Flash', + 17: 'Standard Light A', + 18: 'Standard Light B', + 19: 'Standard Light C', + 20: 'D55', + 21: 'D65', + 22: 'D75', + 255: 'Other'}), + 0x9209: ('Flash', {0: 'No', + 1: 'Fired', + 5: 'Fired (?)', # no return sensed + 7: 'Fired (!)', # return sensed + 9: 'Fill Fired', + 13: 'Fill Fired (?)', + 15: 'Fill Fired (!)', + 16: 'Off', + 24: 'Auto Off', + 25: 'Auto Fired', + 29: 'Auto Fired (?)', + 31: 'Auto Fired (!)', + 32: 'Not Available'}), + 0x920A: ('FocalLength', ), + 0x9214: ('SubjectArea', ), + 0x927C: ('MakerNote', ), + # print as string + 0x9286: ('UserComment', make_string_uc), # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode + 0x9290: ('SubSecTime', ), + 0x9291: ('SubSecTimeOriginal', ), + 0x9292: ('SubSecTimeDigitized', ), + # print as string + 0xA000: ('FlashPixVersion', make_string), + 0xA001: ('ColorSpace', ), + 0xA002: ('ExifImageWidth', ), + 0xA003: ('ExifImageLength', ), + 0xA005: ('InteroperabilityOffset', ), + 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP + 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C - - + 0xA20E: ('FocalPlaneXResolution', ), # 0x920E - - + 0xA20F: ('FocalPlaneYResolution', ), # 0x920F - - + 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 - - + 0xA214: ('SubjectLocation', ), # 0x9214 - - + 0xA215: ('ExposureIndex', ), # 0x9215 - - + 0xA217: ('SensingMethod', ), # 0x9217 - - + 0xA300: ('FileSource', + {3: 'Digital Camera'}), + 0xA301: ('SceneType', + {1: 'Directly Photographed'}), + 0xA302: ('CVAPattern', ), + 0xA401: ('CustomRendered', ), + 0xA402: ('ExposureMode', + {0: 'Auto Exposure', + 1: 'Manual Exposure', + 2: 'Auto Bracket'}), + 0xA403: ('WhiteBalance', + {0: 'Auto', + 1: 'Manual'}), + 0xA404: ('DigitalZoomRatio', ), + 0xA405: ('FocalLengthIn35mm', ), + 0xA406: ('SceneCaptureType', ), + 0xA407: ('GainControl', ), + 0xA408: ('Contrast', ), + 0xA409: ('Saturation', ), + 0xA40A: ('Sharpness', ), + 0xA40C: ('SubjectDistanceRange', ), + } + +# interoperability tags +INTR_TAGS = { + 0x0001: ('InteroperabilityIndex', ), + 0x0002: ('InteroperabilityVersion', ), + 0x1000: ('RelatedImageFileFormat', ), + 0x1001: ('RelatedImageWidth', ), + 0x1002: ('RelatedImageLength', ), + } + +# GPS tags (not used yet, haven't seen camera with GPS) +GPS_TAGS = { + 0x0000: ('GPSVersionID', ), + 0x0001: ('GPSLatitudeRef', ), + 0x0002: ('GPSLatitude', ), + 0x0003: ('GPSLongitudeRef', ), + 0x0004: ('GPSLongitude', ), + 0x0005: ('GPSAltitudeRef', ), + 0x0006: ('GPSAltitude', ), + 0x0007: ('GPSTimeStamp', ), + 0x0008: ('GPSSatellites', ), + 0x0009: ('GPSStatus', ), + 0x000A: ('GPSMeasureMode', ), + 0x000B: ('GPSDOP', ), + 0x000C: ('GPSSpeedRef', ), + 0x000D: ('GPSSpeed', ), + 0x000E: ('GPSTrackRef', ), + 0x000F: ('GPSTrack', ), + 0x0010: ('GPSImgDirectionRef', ), + 0x0011: ('GPSImgDirection', ), + 0x0012: ('GPSMapDatum', ), + 0x0013: ('GPSDestLatitudeRef', ), + 0x0014: ('GPSDestLatitude', ), + 0x0015: ('GPSDestLongitudeRef', ), + 0x0016: ('GPSDestLongitude', ), + 0x0017: ('GPSDestBearingRef', ), + 0x0018: ('GPSDestBearing', ), + 0x0019: ('GPSDestDistanceRef', ), + 0x001A: ('GPSDestDistance', ), + } + +# Ignore these tags when quick processing +# 0x927C is MakerNote Tags +# 0x9286 is user comment +IGNORE_TAGS=(0x9286, 0x927C) + +# http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp +def nikon_ev_bias(seq): + # First digit seems to be in steps of 1/6 EV. + # Does the third value mean the step size? It is usually 6, + # but it is 12 for the ExposureDifference. + # + if seq == [252, 1, 6, 0]: + return "-2/3 EV" + if seq == [253, 1, 6, 0]: + return "-1/2 EV" + if seq == [254, 1, 6, 0]: + return "-1/3 EV" + if seq == [0, 1, 6, 0]: + return "0 EV" + if seq == [2, 1, 6, 0]: + return "+1/3 EV" + if seq == [3, 1, 6, 0]: + return "+1/2 EV" + if seq == [4, 1, 6, 0]: + return "+2/3 EV" + # Handle combinations not in the table. + a = seq[0] + # Causes headaches for the +/- logic, so special case it. + if a == 0: + return "0 EV" + if a > 127: + a = 256 - a + ret_str = "-" + else: + ret_str = "+" + b = seq[2] # Assume third value means the step size + whole = a / b + a = a % b + if whole != 0: + ret_str = ret_str + str(whole) + " " + if a == 0: + ret_str = ret_str + "EV" + else: + r = Ratio(a, b) + ret_str = ret_str + r.__repr__() + " EV" + return ret_str + +# Nikon E99x MakerNote Tags +MAKERNOTE_NIKON_NEWER_TAGS={ + 0x0001: ('MakernoteVersion', make_string), # Sometimes binary + 0x0002: ('ISOSetting', ), + 0x0003: ('ColorMode', ), + 0x0004: ('Quality', ), + 0x0005: ('Whitebalance', ), + 0x0006: ('ImageSharpening', ), + 0x0007: ('FocusMode', ), + 0x0008: ('FlashSetting', ), + 0x0009: ('AutoFlashMode', ), + 0x000B: ('WhiteBalanceBias', ), + 0x000C: ('WhiteBalanceRBCoeff', ), + 0x000D: ('ProgramShift', nikon_ev_bias), + # Nearly the same as the other EV vals, but step size is 1/12 EV (?) + 0x000E: ('ExposureDifference', nikon_ev_bias), + 0x000F: ('ISOSelection', ), + 0x0011: ('NikonPreview', ), + 0x0012: ('FlashCompensation', nikon_ev_bias), + 0x0013: ('ISOSpeedRequested', ), + 0x0016: ('PhotoCornerCoordinates', ), + # 0x0017: Unknown, but most likely an EV value + 0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias), + 0x0019: ('AEBracketCompensationApplied', ), + 0x001A: ('ImageProcessing', ), + 0x0080: ('ImageAdjustment', ), + 0x0081: ('ToneCompensation', ), + 0x0082: ('AuxiliaryLens', ), + 0x0083: ('LensType', ), + 0x0084: ('LensMinMaxFocalMaxAperture', ), + 0x0085: ('ManualFocusDistance', ), + 0x0086: ('DigitalZoomFactor', ), + 0x0087: ('FlashMode', + {0x00: 'Did Not Fire', + 0x01: 'Fired, Manual', + 0x07: 'Fired, External', + 0x08: 'Fired, Commander Mode ', + 0x09: 'Fired, TTL Mode'}), + 0x0088: ('AFFocusPosition', + {0x0000: 'Center', + 0x0100: 'Top', + 0x0200: 'Bottom', + 0x0300: 'Left', + 0x0400: 'Right'}), + 0x0089: ('BracketingMode', + {0x00: 'Single frame, no bracketing', + 0x01: 'Continuous, no bracketing', + 0x02: 'Timer, no bracketing', + 0x10: 'Single frame, exposure bracketing', + 0x11: 'Continuous, exposure bracketing', + 0x12: 'Timer, exposure bracketing', + 0x40: 'Single frame, white balance bracketing', + 0x41: 'Continuous, white balance bracketing', + 0x42: 'Timer, white balance bracketing'}), + 0x008A: ('AutoBracketRelease', ), + 0x008B: ('LensFStops', ), + 0x008C: ('NEFCurve2', ), + 0x008D: ('ColorMode', ), + 0x008F: ('SceneMode', ), + 0x0090: ('LightingType', ), + 0x0091: ('ShotInfo', ), # First 4 bytes are probably a version number in ASCII + 0x0092: ('HueAdjustment', ), + # 0x0093: ('SaturationAdjustment', ), + 0x0094: ('Saturation', # Name conflict with 0x00AA !! + {-3: 'B&W', + -2: '-2', + -1: '-1', + 0: '0', + 1: '1', + 2: '2'}), + 0x0095: ('NoiseReduction', ), + 0x0096: ('NEFCurve2', ), + 0x0097: ('ColorBalance', ), + 0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII + 0x0099: ('RawImageCenter', ), + 0x009A: ('SensorPixelSize', ), + 0x009C: ('Scene Assist', ), + 0x00A0: ('SerialNumber', ), + 0x00A2: ('ImageDataSize', ), + # A4: In NEF, looks like a 4 byte ASCII version number + 0x00A5: ('ImageCount', ), + 0x00A6: ('DeletedImageCount', ), + 0x00A7: ('TotalShutterReleases', ), + # A8: ExposureMode? JPG: First 4 bytes are probably a version number in ASCII + # But in a sample NEF, its 8 zeros, then the string "NORMAL" + 0x00A9: ('ImageOptimization', ), + 0x00AA: ('Saturation', ), + 0x00AB: ('DigitalVariProgram', ), + 0x00AC: ('ImageStabilization', ), + 0x00AD: ('Responsive AF', ), # 'AFResponse' + 0x0010: ('DataDump', ), + } + +MAKERNOTE_NIKON_OLDER_TAGS = { + 0x0003: ('Quality', + {1: 'VGA Basic', + 2: 'VGA Normal', + 3: 'VGA Fine', + 4: 'SXGA Basic', + 5: 'SXGA Normal', + 6: 'SXGA Fine'}), + 0x0004: ('ColorMode', + {1: 'Color', + 2: 'Monochrome'}), + 0x0005: ('ImageAdjustment', + {0: 'Normal', + 1: 'Bright+', + 2: 'Bright-', + 3: 'Contrast+', + 4: 'Contrast-'}), + 0x0006: ('CCDSpeed', + {0: 'ISO 80', + 2: 'ISO 160', + 4: 'ISO 320', + 5: 'ISO 100'}), + 0x0007: ('WhiteBalance', + {0: 'Auto', + 1: 'Preset', + 2: 'Daylight', + 3: 'Incandescent', + 4: 'Fluorescent', + 5: 'Cloudy', + 6: 'Speed Light'}), + } + +# decode Olympus SpecialMode tag in MakerNote +def olympus_special_mode(v): + a={ + 0: 'Normal', + 1: 'Unknown', + 2: 'Fast', + 3: 'Panorama'} + b={ + 0: 'Non-panoramic', + 1: 'Left to right', + 2: 'Right to left', + 3: 'Bottom to top', + 4: 'Top to bottom'} + if v[0] not in a or v[2] not in b: + return v + return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]]) + +MAKERNOTE_OLYMPUS_TAGS={ + # ah HAH! those sneeeeeaky bastids! this is how they get past the fact + # that a JPEG thumbnail is not allowed in an uncompressed TIFF file + 0x0100: ('JPEGThumbnail', ), + 0x0200: ('SpecialMode', olympus_special_mode), + 0x0201: ('JPEGQual', + {1: 'SQ', + 2: 'HQ', + 3: 'SHQ'}), + 0x0202: ('Macro', + {0: 'Normal', + 1: 'Macro', + 2: 'SuperMacro'}), + 0x0203: ('BWMode', + {0: 'Off', + 1: 'On'}), + 0x0204: ('DigitalZoom', ), + 0x0205: ('FocalPlaneDiagonal', ), + 0x0206: ('LensDistortionParams', ), + 0x0207: ('SoftwareRelease', ), + 0x0208: ('PictureInfo', ), + 0x0209: ('CameraID', make_string), # print as string + 0x0F00: ('DataDump', ), + 0x0300: ('PreCaptureFrames', ), + 0x0404: ('SerialNumber', ), + 0x1000: ('ShutterSpeedValue', ), + 0x1001: ('ISOValue', ), + 0x1002: ('ApertureValue', ), + 0x1003: ('BrightnessValue', ), + 0x1004: ('FlashMode', ), + 0x1004: ('FlashMode', + {2: 'On', + 3: 'Off'}), + 0x1005: ('FlashDevice', + {0: 'None', + 1: 'Internal', + 4: 'External', + 5: 'Internal + External'}), + 0x1006: ('ExposureCompensation', ), + 0x1007: ('SensorTemperature', ), + 0x1008: ('LensTemperature', ), + 0x100b: ('FocusMode', + {0: 'Auto', + 1: 'Manual'}), + 0x1017: ('RedBalance', ), + 0x1018: ('BlueBalance', ), + 0x101a: ('SerialNumber', ), + 0x1023: ('FlashExposureComp', ), + 0x1026: ('ExternalFlashBounce', + {0: 'No', + 1: 'Yes'}), + 0x1027: ('ExternalFlashZoom', ), + 0x1028: ('ExternalFlashMode', ), + 0x1029: ('Contrast int16u', + {0: 'High', + 1: 'Normal', + 2: 'Low'}), + 0x102a: ('SharpnessFactor', ), + 0x102b: ('ColorControl', ), + 0x102c: ('ValidBits', ), + 0x102d: ('CoringFilter', ), + 0x102e: ('OlympusImageWidth', ), + 0x102f: ('OlympusImageHeight', ), + 0x1034: ('CompressionRatio', ), + 0x1035: ('PreviewImageValid', + {0: 'No', + 1: 'Yes'}), + 0x1036: ('PreviewImageStart', ), + 0x1037: ('PreviewImageLength', ), + 0x1039: ('CCDScanMode', + {0: 'Interlaced', + 1: 'Progressive'}), + 0x103a: ('NoiseReduction', + {0: 'Off', + 1: 'On'}), + 0x103b: ('InfinityLensStep', ), + 0x103c: ('NearLensStep', ), + + # TODO - these need extra definitions + # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html + 0x2010: ('Equipment', ), + 0x2020: ('CameraSettings', ), + 0x2030: ('RawDevelopment', ), + 0x2040: ('ImageProcessing', ), + 0x2050: ('FocusInfo', ), + 0x3000: ('RawInfo ', ), + } + +# 0x2020 CameraSettings +MAKERNOTE_OLYMPUS_TAG_0x2020={ + 0x0100: ('PreviewImageValid', + {0: 'No', + 1: 'Yes'}), + 0x0101: ('PreviewImageStart', ), + 0x0102: ('PreviewImageLength', ), + 0x0200: ('ExposureMode', { + 1: 'Manual', + 2: 'Program', + 3: 'Aperture-priority AE', + 4: 'Shutter speed priority AE', + 5: 'Program-shift'}), + 0x0201: ('AELock', + {0: 'Off', + 1: 'On'}), + 0x0202: ('MeteringMode', + {2: 'Center Weighted', + 3: 'Spot', + 5: 'ESP', + 261: 'Pattern+AF', + 515: 'Spot+Highlight control', + 1027: 'Spot+Shadow control'}), + 0x0300: ('MacroMode', + {0: 'Off', + 1: 'On'}), + 0x0301: ('FocusMode', + {0: 'Single AF', + 1: 'Sequential shooting AF', + 2: 'Continuous AF', + 3: 'Multi AF', + 10: 'MF'}), + 0x0302: ('FocusProcess', + {0: 'AF Not Used', + 1: 'AF Used'}), + 0x0303: ('AFSearch', + {0: 'Not Ready', + 1: 'Ready'}), + 0x0304: ('AFAreas', ), + 0x0401: ('FlashExposureCompensation', ), + 0x0500: ('WhiteBalance2', + {0: 'Auto', + 16: '7500K (Fine Weather with Shade)', + 17: '6000K (Cloudy)', + 18: '5300K (Fine Weather)', + 20: '3000K (Tungsten light)', + 21: '3600K (Tungsten light-like)', + 33: '6600K (Daylight fluorescent)', + 34: '4500K (Neutral white fluorescent)', + 35: '4000K (Cool white fluorescent)', + 48: '3600K (Tungsten light-like)', + 256: 'Custom WB 1', + 257: 'Custom WB 2', + 258: 'Custom WB 3', + 259: 'Custom WB 4', + 512: 'Custom WB 5400K', + 513: 'Custom WB 2900K', + 514: 'Custom WB 8000K', }), + 0x0501: ('WhiteBalanceTemperature', ), + 0x0502: ('WhiteBalanceBracket', ), + 0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max) + 0x0504: ('ModifiedSaturation', + {0: 'Off', + 1: 'CM1 (Red Enhance)', + 2: 'CM2 (Green Enhance)', + 3: 'CM3 (Blue Enhance)', + 4: 'CM4 (Skin Tones)'}), + 0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max) + 0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max) + 0x0507: ('ColorSpace', + {0: 'sRGB', + 1: 'Adobe RGB', + 2: 'Pro Photo RGB'}), + 0x0509: ('SceneMode', + {0: 'Standard', + 6: 'Auto', + 7: 'Sport', + 8: 'Portrait', + 9: 'Landscape+Portrait', + 10: 'Landscape', + 11: 'Night scene', + 13: 'Panorama', + 16: 'Landscape+Portrait', + 17: 'Night+Portrait', + 19: 'Fireworks', + 20: 'Sunset', + 22: 'Macro', + 25: 'Documents', + 26: 'Museum', + 28: 'Beach&Snow', + 30: 'Candle', + 35: 'Underwater Wide1', + 36: 'Underwater Macro', + 39: 'High Key', + 40: 'Digital Image Stabilization', + 44: 'Underwater Wide2', + 45: 'Low Key', + 46: 'Children', + 48: 'Nature Macro'}), + 0x050a: ('NoiseReduction', + {0: 'Off', + 1: 'Noise Reduction', + 2: 'Noise Filter', + 3: 'Noise Reduction + Noise Filter', + 4: 'Noise Filter (ISO Boost)', + 5: 'Noise Reduction + Noise Filter (ISO Boost)'}), + 0x050b: ('DistortionCorrection', + {0: 'Off', + 1: 'On'}), + 0x050c: ('ShadingCompensation', + {0: 'Off', + 1: 'On'}), + 0x050d: ('CompressionFactor', ), + 0x050f: ('Gradation', + {'-1 -1 1': 'Low Key', + '0 -1 1': 'Normal', + '1 -1 1': 'High Key'}), + 0x0520: ('PictureMode', + {1: 'Vivid', + 2: 'Natural', + 3: 'Muted', + 256: 'Monotone', + 512: 'Sepia'}), + 0x0521: ('PictureModeSaturation', ), + 0x0522: ('PictureModeHue?', ), + 0x0523: ('PictureModeContrast', ), + 0x0524: ('PictureModeSharpness', ), + 0x0525: ('PictureModeBWFilter', + {0: 'n/a', + 1: 'Neutral', + 2: 'Yellow', + 3: 'Orange', + 4: 'Red', + 5: 'Green'}), + 0x0526: ('PictureModeTone', + {0: 'n/a', + 1: 'Neutral', + 2: 'Sepia', + 3: 'Blue', + 4: 'Purple', + 5: 'Green'}), + 0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits + 0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number) + 0x0603: ('ImageQuality2', + {1: 'SQ', + 2: 'HQ', + 3: 'SHQ', + 4: 'RAW'}), + 0x0901: ('ManometerReading', ), + } + + +MAKERNOTE_CASIO_TAGS={ + 0x0001: ('RecordingMode', + {1: 'Single Shutter', + 2: 'Panorama', + 3: 'Night Scene', + 4: 'Portrait', + 5: 'Landscape'}), + 0x0002: ('Quality', + {1: 'Economy', + 2: 'Normal', + 3: 'Fine'}), + 0x0003: ('FocusingMode', + {2: 'Macro', + 3: 'Auto Focus', + 4: 'Manual Focus', + 5: 'Infinity'}), + 0x0004: ('FlashMode', + {1: 'Auto', + 2: 'On', + 3: 'Off', + 4: 'Red Eye Reduction'}), + 0x0005: ('FlashIntensity', + {11: 'Weak', + 13: 'Normal', + 15: 'Strong'}), + 0x0006: ('Object Distance', ), + 0x0007: ('WhiteBalance', + {1: 'Auto', + 2: 'Tungsten', + 3: 'Daylight', + 4: 'Fluorescent', + 5: 'Shade', + 129: 'Manual'}), + 0x000B: ('Sharpness', + {0: 'Normal', + 1: 'Soft', + 2: 'Hard'}), + 0x000C: ('Contrast', + {0: 'Normal', + 1: 'Low', + 2: 'High'}), + 0x000D: ('Saturation', + {0: 'Normal', + 1: 'Low', + 2: 'High'}), + 0x0014: ('CCDSpeed', + {64: 'Normal', + 80: 'Normal', + 100: 'High', + 125: '+1.0', + 244: '+3.0', + 250: '+2.0'}), + } + +MAKERNOTE_FUJIFILM_TAGS={ + 0x0000: ('NoteVersion', make_string), + 0x1000: ('Quality', ), + 0x1001: ('Sharpness', + {1: 'Soft', + 2: 'Soft', + 3: 'Normal', + 4: 'Hard', + 5: 'Hard'}), + 0x1002: ('WhiteBalance', + {0: 'Auto', + 256: 'Daylight', + 512: 'Cloudy', + 768: 'DaylightColor-Fluorescent', + 769: 'DaywhiteColor-Fluorescent', + 770: 'White-Fluorescent', + 1024: 'Incandescent', + 3840: 'Custom'}), + 0x1003: ('Color', + {0: 'Normal', + 256: 'High', + 512: 'Low'}), + 0x1004: ('Tone', + {0: 'Normal', + 256: 'High', + 512: 'Low'}), + 0x1010: ('FlashMode', + {0: 'Auto', + 1: 'On', + 2: 'Off', + 3: 'Red Eye Reduction'}), + 0x1011: ('FlashStrength', ), + 0x1020: ('Macro', + {0: 'Off', + 1: 'On'}), + 0x1021: ('FocusMode', + {0: 'Auto', + 1: 'Manual'}), + 0x1030: ('SlowSync', + {0: 'Off', + 1: 'On'}), + 0x1031: ('PictureMode', + {0: 'Auto', + 1: 'Portrait', + 2: 'Landscape', + 4: 'Sports', + 5: 'Night', + 6: 'Program AE', + 256: 'Aperture Priority AE', + 512: 'Shutter Priority AE', + 768: 'Manual Exposure'}), + 0x1100: ('MotorOrBracket', + {0: 'Off', + 1: 'On'}), + 0x1300: ('BlurWarning', + {0: 'Off', + 1: 'On'}), + 0x1301: ('FocusWarning', + {0: 'Off', + 1: 'On'}), + 0x1302: ('AEWarning', + {0: 'Off', + 1: 'On'}), + } + +MAKERNOTE_CANON_TAGS = { + 0x0006: ('ImageType', ), + 0x0007: ('FirmwareVersion', ), + 0x0008: ('ImageNumber', ), + 0x0009: ('OwnerName', ), + } + +# this is in element offset, name, optional value dictionary format +MAKERNOTE_CANON_TAG_0x001 = { + 1: ('Macromode', + {1: 'Macro', + 2: 'Normal'}), + 2: ('SelfTimer', ), + 3: ('Quality', + {2: 'Normal', + 3: 'Fine', + 5: 'Superfine'}), + 4: ('FlashMode', + {0: 'Flash Not Fired', + 1: 'Auto', + 2: 'On', + 3: 'Red-Eye Reduction', + 4: 'Slow Synchro', + 5: 'Auto + Red-Eye Reduction', + 6: 'On + Red-Eye Reduction', + 16: 'external flash'}), + 5: ('ContinuousDriveMode', + {0: 'Single Or Timer', + 1: 'Continuous'}), + 7: ('FocusMode', + {0: 'One-Shot', + 1: 'AI Servo', + 2: 'AI Focus', + 3: 'MF', + 4: 'Single', + 5: 'Continuous', + 6: 'MF'}), + 10: ('ImageSize', + {0: 'Large', + 1: 'Medium', + 2: 'Small'}), + 11: ('EasyShootingMode', + {0: 'Full Auto', + 1: 'Manual', + 2: 'Landscape', + 3: 'Fast Shutter', + 4: 'Slow Shutter', + 5: 'Night', + 6: 'B&W', + 7: 'Sepia', + 8: 'Portrait', + 9: 'Sports', + 10: 'Macro/Close-Up', + 11: 'Pan Focus'}), + 12: ('DigitalZoom', + {0: 'None', + 1: '2x', + 2: '4x'}), + 13: ('Contrast', + {0xFFFF: 'Low', + 0: 'Normal', + 1: 'High'}), + 14: ('Saturation', + {0xFFFF: 'Low', + 0: 'Normal', + 1: 'High'}), + 15: ('Sharpness', + {0xFFFF: 'Low', + 0: 'Normal', + 1: 'High'}), + 16: ('ISO', + {0: 'See ISOSpeedRatings Tag', + 15: 'Auto', + 16: '50', + 17: '100', + 18: '200', + 19: '400'}), + 17: ('MeteringMode', + {3: 'Evaluative', + 4: 'Partial', + 5: 'Center-weighted'}), + 18: ('FocusType', + {0: 'Manual', + 1: 'Auto', + 3: 'Close-Up (Macro)', + 8: 'Locked (Pan Mode)'}), + 19: ('AFPointSelected', + {0x3000: 'None (MF)', + 0x3001: 'Auto-Selected', + 0x3002: 'Right', + 0x3003: 'Center', + 0x3004: 'Left'}), + 20: ('ExposureMode', + {0: 'Easy Shooting', + 1: 'Program', + 2: 'Tv-priority', + 3: 'Av-priority', + 4: 'Manual', + 5: 'A-DEP'}), + 23: ('LongFocalLengthOfLensInFocalUnits', ), + 24: ('ShortFocalLengthOfLensInFocalUnits', ), + 25: ('FocalUnitsPerMM', ), + 28: ('FlashActivity', + {0: 'Did Not Fire', + 1: 'Fired'}), + 29: ('FlashDetails', + {14: 'External E-TTL', + 13: 'Internal Flash', + 11: 'FP Sync Used', + 7: '2nd("Rear")-Curtain Sync Used', + 4: 'FP Sync Enabled'}), + 32: ('FocusMode', + {0: 'Single', + 1: 'Continuous'}), + } + +MAKERNOTE_CANON_TAG_0x004 = { + 7: ('WhiteBalance', + {0: 'Auto', + 1: 'Sunny', + 2: 'Cloudy', + 3: 'Tungsten', + 4: 'Fluorescent', + 5: 'Flash', + 6: 'Custom'}), + 9: ('SequenceNumber', ), + 14: ('AFPointUsed', ), + 15: ('FlashBias', + {0XFFC0: '-2 EV', + 0XFFCC: '-1.67 EV', + 0XFFD0: '-1.50 EV', + 0XFFD4: '-1.33 EV', + 0XFFE0: '-1 EV', + 0XFFEC: '-0.67 EV', + 0XFFF0: '-0.50 EV', + 0XFFF4: '-0.33 EV', + 0X0000: '0 EV', + 0X000C: '0.33 EV', + 0X0010: '0.50 EV', + 0X0014: '0.67 EV', + 0X0020: '1 EV', + 0X002C: '1.33 EV', + 0X0030: '1.50 EV', + 0X0034: '1.67 EV', + 0X0040: '2 EV'}), + 19: ('SubjectDistance', ), + } + +# extract multibyte integer in Motorola format (little endian) +def s2n_motorola(str): + x = 0 + for c in str: + x = (x << 8) | ord(c) + return x + +# extract multibyte integer in Intel format (big endian) +def s2n_intel(str): + x = 0 + y = 0L + for c in str: + x = x | (ord(c) << y) + y = y + 8 + return x + +# ratio object that eventually will be able to reduce itself to lowest +# common denominator for printing +def gcd(a, b): + if b == 0: + return a + else: + return gcd(b, a % b) + +class Ratio: + def __init__(self, num, den): + self.num = num + self.den = den + + def __repr__(self): + self.reduce() + if self.den == 1: + return str(self.num) + return '%d/%d' % (self.num, self.den) + + def reduce(self): + div = gcd(self.num, self.den) + if div > 1: + self.num = self.num / div + self.den = self.den / div + +# for ease of dealing with tags +class IFD_Tag: + def __init__(self, printable, tag, field_type, values, field_offset, + field_length): + # printable version of data + self.printable = printable + # tag ID number + self.tag = tag + # field type as index into FIELD_TYPES + self.field_type = field_type + # offset of start of field in bytes from beginning of IFD + self.field_offset = field_offset + # length of data field in bytes + self.field_length = field_length + # either a string or array of data items + self.values = values + + def __str__(self): + return self.printable + + def __repr__(self): + return '(0x%04X) %s=%s @ %d' % (self.tag, + FIELD_TYPES[self.field_type][2], + self.printable, + self.field_offset) + +# class that handles an EXIF header +class EXIF_header: + def __init__(self, file, endian, offset, fake_exif, debug=0): + self.file = file + self.endian = endian + self.offset = offset + self.fake_exif = fake_exif + self.debug = debug + self.tags = {} + + # convert slice to integer, based on sign and endian flags + # usually this offset is assumed to be relative to the beginning of the + # start of the EXIF information. For some cameras that use relative tags, + # this offset may be relative to some other starting point. + def s2n(self, offset, length, signed=0): + self.file.seek(self.offset+offset) + slice=self.file.read(length) + if self.endian == 'I': + val=s2n_intel(slice) + else: + val=s2n_motorola(slice) + # Sign extension ? + if signed: + msb=1L << (8*length-1) + if val & msb: + val=val-(msb << 1) + return val + + # convert offset to string + def n2s(self, offset, length): + s = '' + for dummy in range(length): + if self.endian == 'I': + s = s + chr(offset & 0xFF) + else: + s = chr(offset & 0xFF) + s + offset = offset >> 8 + return s + + # return first IFD + def first_IFD(self): + return self.s2n(4, 4) + + # return pointer to next IFD + def next_IFD(self, ifd): + entries=self.s2n(ifd, 2) + return self.s2n(ifd+2+12*entries, 4) + + # return list of IFDs in header + def list_IFDs(self): + i=self.first_IFD() + a=[] + while i: + a.append(i) + i=self.next_IFD(i) + return a + + # return list of entries in this IFD + def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0, stop_tag='UNDEF'): + entries=self.s2n(ifd, 2) + for i in range(entries): + # entry is index of start of this IFD in the file + entry = ifd + 2 + 12 * i + tag = self.s2n(entry, 2) + + # get tag name early to avoid errors, help debug + tag_entry = dict.get(tag) + if tag_entry: + tag_name = tag_entry[0] + else: + tag_name = 'Tag 0x%04X' % tag + + # ignore certain tags for faster processing + if not (not detailed and tag in IGNORE_TAGS): + field_type = self.s2n(entry + 2, 2) + if not 0 < field_type < len(FIELD_TYPES): + # unknown field type + raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag)) + typelen = FIELD_TYPES[field_type][0] + count = self.s2n(entry + 4, 4) + offset = entry + 8 + if count * typelen > 4: + # offset is not the value; it's a pointer to the value + # if relative we set things up so s2n will seek to the right + # place when it adds self.offset. Note that this 'relative' + # is for the Nikon type 3 makernote. Other cameras may use + # other relative offsets, which would have to be computed here + # slightly differently. + if relative: + tmp_offset = self.s2n(offset, 4) + offset = tmp_offset + ifd - self.offset + 4 + if self.fake_exif: + offset = offset + 18 + else: + offset = self.s2n(offset, 4) + field_offset = offset + if field_type == 2: + # special case: null-terminated ASCII string + if count != 0: + self.file.seek(self.offset + offset) + values = self.file.read(count) + values = values.strip().replace('\x00', '') + else: + values = '' + else: + values = [] + signed = (field_type in [6, 8, 9, 10]) + for dummy in range(count): + if field_type in (5, 10): + # a ratio + value = Ratio(self.s2n(offset, 4, signed), + self.s2n(offset + 4, 4, signed)) + else: + value = self.s2n(offset, typelen, signed) + values.append(value) + offset = offset + typelen + # now "values" is either a string or an array + if count == 1 and field_type != 2: + printable=str(values[0]) + else: + printable=str(values) + # compute printable version of values + if tag_entry: + if len(tag_entry) != 1: + # optional 2nd tag element is present + if callable(tag_entry[1]): + # call mapping function + printable = tag_entry[1](values) + else: + printable = '' + for i in values: + # use lookup table for this tag + printable += tag_entry[1].get(i, repr(i)) + + self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag, + field_type, + values, field_offset, + count * typelen) + if self.debug: + print ' debug: %s: %s' % (tag_name, + repr(self.tags[ifd_name + ' ' + tag_name])) + + if tag_name == stop_tag: + break + + # extract uncompressed TIFF thumbnail (like pulling teeth) + # we take advantage of the pre-existing layout in the thumbnail IFD as + # much as possible + def extract_TIFF_thumbnail(self, thumb_ifd): + entries = self.s2n(thumb_ifd, 2) + # this is header plus offset to IFD ... + if self.endian == 'M': + tiff = 'MM\x00*\x00\x00\x00\x08' + else: + tiff = 'II*\x00\x08\x00\x00\x00' + # ... plus thumbnail IFD data plus a null "next IFD" pointer + self.file.seek(self.offset+thumb_ifd) + tiff += self.file.read(entries*12+2)+'\x00\x00\x00\x00' + + # fix up large value offset pointers into data area + for i in range(entries): + entry = thumb_ifd + 2 + 12 * i + tag = self.s2n(entry, 2) + field_type = self.s2n(entry+2, 2) + typelen = FIELD_TYPES[field_type][0] + count = self.s2n(entry+4, 4) + oldoff = self.s2n(entry+8, 4) + # start of the 4-byte pointer area in entry + ptr = i * 12 + 18 + # remember strip offsets location + if tag == 0x0111: + strip_off = ptr + strip_len = count * typelen + # is it in the data area? + if count * typelen > 4: + # update offset pointer (nasty "strings are immutable" crap) + # should be able to say "tiff[ptr:ptr+4]=newoff" + newoff = len(tiff) + tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:] + # remember strip offsets location + if tag == 0x0111: + strip_off = newoff + strip_len = 4 + # get original data and store it + self.file.seek(self.offset + oldoff) + tiff += self.file.read(count * typelen) + + # add pixel strips and update strip offset info + old_offsets = self.tags['Thumbnail StripOffsets'].values + old_counts = self.tags['Thumbnail StripByteCounts'].values + for i in range(len(old_offsets)): + # update offset pointer (more nasty "strings are immutable" crap) + offset = self.n2s(len(tiff), strip_len) + tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:] + strip_off += strip_len + # add pixel strip to end + self.file.seek(self.offset + old_offsets[i]) + tiff += self.file.read(old_counts[i]) + + self.tags['TIFFThumbnail'] = tiff + + # decode all the camera-specific MakerNote formats + + # Note is the data that comprises this MakerNote. The MakerNote will + # likely have pointers in it that point to other parts of the file. We'll + # use self.offset as the starting point for most of those pointers, since + # they are relative to the beginning of the file. + # + # If the MakerNote is in a newer format, it may use relative addressing + # within the MakerNote. In that case we'll use relative addresses for the + # pointers. + # + # As an aside: it's not just to be annoying that the manufacturers use + # relative offsets. It's so that if the makernote has to be moved by the + # picture software all of the offsets don't have to be adjusted. Overall, + # this is probably the right strategy for makernotes, though the spec is + # ambiguous. (The spec does not appear to imagine that makernotes would + # follow EXIF format internally. Once they did, it's ambiguous whether + # the offsets should be from the header at the start of all the EXIF info, + # or from the header at the start of the makernote.) + def decode_maker_note(self): + note = self.tags['EXIF MakerNote'] + make = self.tags['Image Make'].printable + # model = self.tags['Image Model'].printable # unused + + # Nikon + # The maker note usually starts with the word Nikon, followed by the + # type of the makernote (1 or 2, as a short). If the word Nikon is + # not at the start of the makernote, it's probably type 2, since some + # cameras work that way. + if make in ('NIKON', 'NIKON CORPORATION'): + if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]: + if self.debug: + print "Looks like a type 1 Nikon MakerNote." + self.dump_IFD(note.field_offset+8, 'MakerNote', + dict=MAKERNOTE_NIKON_OLDER_TAGS) + elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]: + if self.debug: + print "Looks like a labeled type 2 Nikon MakerNote" + if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]: + raise ValueError("Missing marker tag '42' in MakerNote.") + # skip the Makernote label and the TIFF header + self.dump_IFD(note.field_offset+10+8, 'MakerNote', + dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1) + else: + # E99x or D1 + if self.debug: + print "Looks like an unlabeled type 2 Nikon MakerNote" + self.dump_IFD(note.field_offset, 'MakerNote', + dict=MAKERNOTE_NIKON_NEWER_TAGS) + return + + # Olympus + if make.startswith('OLYMPUS'): + self.dump_IFD(note.field_offset+8, 'MakerNote', + dict=MAKERNOTE_OLYMPUS_TAGS) + # TODO + #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),): + # self.decode_olympus_tag(self.tags[i[0]].values, i[1]) + #return + + # Casio + if make == 'Casio': + self.dump_IFD(note.field_offset, 'MakerNote', + dict=MAKERNOTE_CASIO_TAGS) + return + + # Fujifilm + if make == 'FUJIFILM': + # bug: everything else is "Motorola" endian, but the MakerNote + # is "Intel" endian + endian = self.endian + self.endian = 'I' + # bug: IFD offsets are from beginning of MakerNote, not + # beginning of file header + offset = self.offset + self.offset += note.field_offset + # process note with bogus values (note is actually at offset 12) + self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS) + # reset to correct values + self.endian = endian + self.offset = offset + return + + # Canon + if make == 'Canon': + self.dump_IFD(note.field_offset, 'MakerNote', + dict=MAKERNOTE_CANON_TAGS) + for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001), + ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)): + self.canon_decode_tag(self.tags[i[0]].values, i[1]) + return + + # decode Olympus MakerNote tag based on offset within tag + def olympus_decode_tag(self, value, dict): + pass + + # decode Canon MakerNote tag based on offset within tag + # see http://www.burren.cx/david/canon.html by David Burren + def canon_decode_tag(self, value, dict): + for i in range(1, len(value)): + x=dict.get(i, ('Unknown', )) + if self.debug: + print i, x + name=x[0] + if len(x) > 1: + val=x[1].get(value[i], 'Unknown') + else: + val=value[i] + # it's not a real IFD Tag but we fake one to make everybody + # happy. this will have a "proprietary" type + self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None, + None, None) + +# process an image file (expects an open file object) +# this is the function that has to deal with all the arbitrary nasty bits +# of the EXIF standard +def process_file(f, stop_tag='UNDEF', details=True, debug=False): + # yah it's cheesy... + global detailed + detailed = details + + # by default do not fake an EXIF beginning + fake_exif = 0 + + # determine whether it's a JPEG or TIFF + data = f.read(12) + if data[0:4] in ['II*\x00', 'MM\x00*']: + # it's a TIFF file + f.seek(0) + endian = f.read(1) + f.read(1) + offset = 0 + elif data[0:2] == '\xFF\xD8': + # it's a JPEG file + while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'): + length = ord(data[4])*256+ord(data[5]) + f.read(length-8) + # fake an EXIF beginning of file + data = '\xFF\x00'+f.read(10) + fake_exif = 1 + if data[2] == '\xFF' and data[6:10] == 'Exif': + # detected EXIF header + offset = f.tell() + endian = f.read(1) + else: + # no EXIF information + return {} + else: + # file format not recognized + return {} + + # deal with the EXIF info we found + if debug: + print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format' + hdr = EXIF_header(f, endian, offset, fake_exif, debug) + ifd_list = hdr.list_IFDs() + ctr = 0 + for i in ifd_list: + if ctr == 0: + IFD_name = 'Image' + elif ctr == 1: + IFD_name = 'Thumbnail' + thumb_ifd = i + else: + IFD_name = 'IFD %d' % ctr + if debug: + print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i) + hdr.dump_IFD(i, IFD_name, stop_tag=stop_tag) + # EXIF IFD + exif_off = hdr.tags.get(IFD_name+' ExifOffset') + if exif_off: + if debug: + print ' EXIF SubIFD at offset %d:' % exif_off.values[0] + hdr.dump_IFD(exif_off.values[0], 'EXIF', stop_tag=stop_tag) + # Interoperability IFD contained in EXIF IFD + intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset') + if intr_off: + if debug: + print ' EXIF Interoperability SubSubIFD at offset %d:' \ + % intr_off.values[0] + hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability', + dict=INTR_TAGS, stop_tag=stop_tag) + # GPS IFD + gps_off = hdr.tags.get(IFD_name+' GPSInfo') + if gps_off: + if debug: + print ' GPS SubIFD at offset %d:' % gps_off.values[0] + hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS, stop_tag=stop_tag) + ctr += 1 + + # extract uncompressed TIFF thumbnail + thumb = hdr.tags.get('Thumbnail Compression') + if thumb and thumb.printable == 'Uncompressed TIFF': + hdr.extract_TIFF_thumbnail(thumb_ifd) + + # JPEG thumbnail (thankfully the JPEG data is stored as a unit) + thumb_off = hdr.tags.get('Thumbnail JPEGInterchangeFormat') + if thumb_off: + f.seek(offset+thumb_off.values[0]) + size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0] + hdr.tags['JPEGThumbnail'] = f.read(size) + + # deal with MakerNote contained in EXIF IFD + if 'EXIF MakerNote' in hdr.tags and detailed: + hdr.decode_maker_note() + + # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote + # since it's not allowed in a uncompressed TIFF IFD + if 'JPEGThumbnail' not in hdr.tags: + thumb_off=hdr.tags.get('MakerNote JPEGThumbnail') + if thumb_off: + f.seek(offset+thumb_off.values[0]) + hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length) + + return hdr.tags + + +# show command line usage +def usage(exit_status): + msg = 'Usage: EXIF.py [OPTIONS] file1 [file2 ...]\n' + msg += 'Extract EXIF information from digital camera image files.\n\nOptions:\n' + msg += '-q --quick Do not process MakerNotes.\n' + msg += '-t TAG --stop-tag TAG Stop processing when this tag is retrieved.\n' + msg += '-d --debug Run in debug mode.\n' + print msg + sys.exit(exit_status) + +# library test/debug function (dump given files) +if __name__ == '__main__': + import sys + import getopt + + # parse command line options/arguments + try: + opts, args = getopt.getopt(sys.argv[1:], "hqdt:v", ["help", "quick", "debug", "stop-tag="]) + except getopt.GetoptError: + usage(2) + if args == []: + usage(2) + detailed = True + stop_tag = 'UNDEF' + debug = False + for o, a in opts: + if o in ("-h", "--help"): + usage(0) + if o in ("-q", "--quick"): + detailed = False + if o in ("-t", "--stop-tag"): + stop_tag = a + if o in ("-d", "--debug"): + debug = True + + # output info for each file + for filename in args: + try: + file=open(filename, 'rb') + except: + print "'%s' is unreadable\n"%filename + continue + print filename + ':' + # get the tags + data = process_file(file, stop_tag=stop_tag, details=detailed, debug=debug) + if not data: + print 'No EXIF information found' + continue + + x=data.keys() + x.sort() + for i in x: + if i in ('JPEGThumbnail', 'TIFFThumbnail'): + continue + try: + print ' %s (%s): %s' % \ + (i, FIELD_TYPES[data[i].field_type][2], data[i].printable) + except: + print 'error', i, '"', data[i], '"' + if 'JPEGThumbnail' in data: + print 'File has JPEG thumbnail' + print +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/utils/reflection.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,93 @@ +""" Function for generating web 2.0 style image reflection effects. + +Copyright (c) 2007, Justin C. Driscoll +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of reflection.py nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" + +try: + import Image + import ImageColor +except ImportError: + try: + from PIL import Image + from PIL import ImageColor + except ImportError: + raise ImportError("The Python Imaging Library was not found.") + + +def add_reflection(im, bgcolor="#00000", amount=0.4, opacity=0.6): + """ Returns the supplied PIL Image (im) with a reflection effect + + bgcolor The background color of the reflection gradient + amount The height of the reflection as a percentage of the orignal image + opacity The initial opacity of the reflection gradient + + Originally written for the Photologue image management system for Django + and Based on the original concept by Bernd Schlapsi + + """ + # convert bgcolor string to rgb value + background_color = ImageColor.getrgb(bgcolor) + + # copy orignial image and flip the orientation + reflection = im.copy().transpose(Image.FLIP_TOP_BOTTOM) + + # create a new image filled with the bgcolor the same size + background = Image.new("RGB", im.size, background_color) + + # calculate our alpha mask + start = int(255 - (255 * opacity)) # The start of our gradient + steps = int(255 * amount) # the number of intermedite values + increment = (255 - start) / float(steps) + mask = Image.new('L', (1, 255)) + for y in range(255): + if y < steps: + val = int(y * increment + start) + else: + val = 255 + mask.putpixel((0, y), val) + alpha_mask = mask.resize(im.size) + + # merge the reflection onto our background color using the alpha mask + reflection = Image.composite(background, reflection, alpha_mask) + + # crop the reflection + reflection_height = int(im.size[1] * amount) + reflection = reflection.crop((0, 0, im.size[0], reflection_height)) + + # create new image sized to hold both the original image and the reflection + composite = Image.new("RGB", (im.size[0], im.size[1]+reflection_height), background_color) + + # paste the orignal image and the reflection into the composite image + composite.paste(im, (0, 0)) + composite.paste(reflection, (0, im.size[1])) + + # return the image complete with reflection effect + return composite + \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/photologue/utils/watermark.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,64 @@ +""" Function for applying watermarks to images. + +Original found here: +http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/362879 + +""" + +try: + import Image + import ImageEnhance +except ImportError: + try: + from PIL import Image + from PIL import ImageEnhance + except ImportError: + raise ImportError("The Python Imaging Library was not found.") + +def reduce_opacity(im, opacity): + """Returns an image with reduced opacity.""" + assert opacity >= 0 and opacity <= 1 + if im.mode != 'RGBA': + im = im.convert('RGBA') + else: + im = im.copy() + alpha = im.split()[3] + alpha = ImageEnhance.Brightness(alpha).enhance(opacity) + im.putalpha(alpha) + return im + +def apply_watermark(im, mark, position, opacity=1): + """Adds a watermark to an image.""" + if opacity < 1: + mark = reduce_opacity(mark, opacity) + if im.mode != 'RGBA': + im = im.convert('RGBA') + # create a transparent layer the size of the image and draw the + # watermark in that layer. + layer = Image.new('RGBA', im.size, (0,0,0,0)) + if position == 'tile': + for y in range(0, im.size[1], mark.size[1]): + for x in range(0, im.size[0], mark.size[0]): + layer.paste(mark, (x, y)) + elif position == 'scale': + # scale, but preserve the aspect ratio + ratio = min( + float(im.size[0]) / mark.size[0], float(im.size[1]) / mark.size[1]) + w = int(mark.size[0] * ratio) + h = int(mark.size[1] * ratio) + mark = mark.resize((w, h)) + layer.paste(mark, ((im.size[0] - w) / 2, (im.size[1] - h) / 2)) + else: + layer.paste(mark, position) + # composite the watermark with the layer + return Image.composite(layer, im, layer) + +def test(): + im = Image.open('test.png') + mark = Image.open('overlay.png') + watermark(im, mark, 'tile', 0.5).show() + watermark(im, mark, 'scale', 1.0).show() + watermark(im, mark, (100, 100), 0.5).show() + +if __name__ == '__main__': + test()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/pl-admin.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,116 @@ +import getopt, sys + +try: + import settings # Assumed to be in the same directory. + from django.core.management import setup_environ +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + + +def precache(sizes=[], reset=False): + # setup django environment + setup_environ(settings) + + # import models + from photologue.models import Photo, PhotoSize, PhotoSizeCache + + cache = PhotoSizeCache() + + print 'Caching photos, this may take a while...' + + for photo in Photo.objects.all(): + if len(sizes): + for size in sizes: + photosize = cache.sizes.get(size, None) + if photosize is None: + print '\nA photosize named "%s" was not found...' % size + else: + if reset: + photo.remove_size(photosize) + photo.create_size(photosize) + else: + for size in caches.sizes.values(): + if reset: + Photo.remove_size(photosize) + photo.create_size(photosize) + + print ' Complete.' + sys.exit(2) + + +def reset(): + # setup django environment + setup_environ(settings) + + # import models + from photologue.models import Photo, PhotoSize + + print 'Reseting photo cache, this may take a while...' + + for photo in Photo.objects.all(): + photo.clear_cache() + + print ' Complete.' + sys.exit(2) + + +def usage(): + print """ + +pl-admin.py - Photologue administration script. + +Available Commands: + pl-admin.py create Resizes and caches all defined photo sizes for each image. + pl-admin.py reset Removes all cached images. + +Options: + --reset (-r) If calling create the script will clear the existing photo cache + before regenerating the specified size (or sizes) + --size (-s) The name of a photosize generate + +Usage: + pl-admin.py [options] command + +Examples: + pl-admin.py -r -s=thumbnail create + pl-admin.py -s=thumbnail -s=display create + pl-admin.py reset + +""" + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], "hrs:", + ["help", "reset", "sizes="]) + except getopt.GetoptError, err: + print str(err) + usage() + sys.exit(2) + r = False + s = [] + for o, a in opts: + if o in ("-h", "--help"): + usage() + sys.exit(2) + if o in ("-r", "--reset"): + r = True + elif o in ("-s", "--sizes"): + s.append(a.strip('=')) + else: + usage() + sys.exit(2) + + if len(args) == 1: + command = args[0] + if command == 'create': + precache(s, r) + elif command == 'reset': + reset() + + usage() + + +if __name__ == '__main__': + main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/settings/base.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,133 @@ +# Base Django settings for madeira project. + +import os +import django.utils.simplejson as json + +PROJECT_PATH = os.path.abspath(os.path.join(os.path.split(__file__)[0], '..')) + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = [ + ('Brian Neal', 'admin@surfguitar101.com'), +] + +MANAGERS = ADMINS + +INTERNAL_IPS = ['127.0.0.1'] + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = False + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = os.path.abspath(os.path.join(PROJECT_PATH, '..', 'media')) + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '/media/' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/static/admin/' + +# Staticfiles settings: +STATICFILES_DIRS = [ + os.path.abspath(os.path.join(PROJECT_PATH, '..', 'static')), +] +STATIC_ROOT = '/tmp/test_madeira_static_root' +STATIC_URL = '/static/' + +# Make this unique, and don't share it with anybody. +SECRETS = json.load(open(os.path.join(PROJECT_PATH, 'settings', 'secrets.json'))) +SECRET_KEY = SECRETS['SECRET_KEY'] + +TEMPLATE_LOADERS = [ + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +] + +MIDDLEWARE_CLASSES = [ + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.middleware.doc.XViewMiddleware', + 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', +] + +ROOT_URLCONF = 'madeira.urls' + +# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". +# Always use forward slashes, even on Windows. +# Don't forget to use absolute paths, not relative paths. +TEMPLATE_DIRS = [ + os.path.join(PROJECT_PATH, 'templates'), + os.path.join(PROJECT_PATH, 'templates', 'band'), + os.path.join(PROJECT_PATH, 'photologue', 'templates'), +] + +TEMPLATE_CONTEXT_PROCESSORS = [ + "django.contrib.auth.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.request", + "django.core.context_processors.media", + "django.core.context_processors.static", + "django.contrib.messages.context_processors.messages", +] + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.admindocs', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.flatpages', + 'django.contrib.markup', + 'django.contrib.messages', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.staticfiles', + 'band', + 'photologue', +] + +####################################################################### +# Messages +####################################################################### +MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' + +####################################################################### +# Email +####################################################################### +EMAIL_HOST = 'localhost' +EMAIL_PORT = 1025 + +####################################################################### +# Sessions +####################################################################### +SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" +SESSION_COOKIE_AGE = 2 * 7 * 24 * 60 * 60 # 2 weeks in seconds +SESSION_COOKIE_DOMAIN = None +SESSION_COOKIE_NAME = 'madeira_sessionid' +SESSION_COOKIE_PATH = '/' +SESSION_COOKIE_SECURE = False +SESSION_EXPIRE_AT_BROWSER_CLOSE = False +SESSION_SAVE_EVERY_REQUEST = False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/settings/local.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,77 @@ +""" +Local Django settings for The Madeira site. +The contents of this file will vary depending on the local installation. + +""" +from settings.base import * + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': SECRETS['DB_NAME'], + 'USER': SECRETS['DB_USER'], + 'PASSWORD': SECRETS['DB_PASSWORD'], + }, +} + +# Django Debug Toolbar support +if DEBUG: + try: + import debug_toolbar + except ImportError: + pass + else: + i = MIDDLEWARE_CLASSES.index('django.middleware.common.CommonMiddleware') + MIDDLEWARE_CLASSES.insert(i + 1, + 'debug_toolbar.middleware.DebugToolbarMiddleware') + INSTALLED_APPS.append('debug_toolbar') + DEBUG_TOOLBAR_CONFIG = { + 'INTERCEPT_REDIRECTS': True, + } + +# Logging configuration + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': True, + 'formatters': { + 'verbose': { + 'format': '%(asctime)s %(levelname)s %(module)s %(process)d %(thread)d %(message)s' + }, + 'simple': { + 'format': '%(asctime)s %(levelname)s %(message)s' + }, + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'level': 'DEBUG', + 'formatter': 'simple', + }, + 'file': { + 'class': 'logging.handlers.RotatingFileHandler', + 'level': 'DEBUG', + 'formatter': 'simple', + 'filename': os.path.join(PROJECT_PATH, 'logs', 'madeira.log'), + 'mode': 'a', + 'maxBytes': 100 * 1024, + 'backupCount': 10, + }, + 'mail_admins': { + 'class': 'django.utils.log.AdminEmailHandler', + 'level': 'ERROR', + 'formatter': 'simple', + }, + }, + 'loggers': { + 'django':{ + 'level': 'WARNING', + 'propagate': False, + 'handlers': ['file'], + }, + }, + 'root': { + 'level': 'DEBUG', + 'handlers': ['file'], + }, +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/settings/production.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,91 @@ +# Django production settings for the madeira project. + +from settings.base import * + +DEBUG = False +TEMPLATE_DEBUG = DEBUG + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'madeira_django', + 'USER': SECRETS['DB_USER'], + 'PASSWORD': SECRETS['DB_PASSWORD'], + }, +} + +STATIC_ROOT = os.path.abspath(os.path.join(PROJECT_PATH, '..', 'static_serve')) + +TEMPLATE_LOADERS = [ + ('django.template.loaders.cached.Loader', ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + )), +] + +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': '127.0.0.1:11211', + 'TIMEOUT': 600, + }, +} +CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True +CACHE_MIDDLEWARE_SECONDS = 600 +CACHE_MIDDLEWARE_KEY_PREFIX = '' + +MIDDLEWARE_CLASSES.insert(0, 'django.middleware.cache.UpdateCacheMiddleware') +MIDDLEWARE_CLASSES.append('django.middleware.cache.FetchFromCacheMiddleware') + +EMAIL_HOST = 'localhost' +EMAIL_PORT = 25 + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': True, + 'formatters': { + 'verbose': { + 'format': '%(asctime)s %(levelname)s %(module)s %(process)d %(thread)d %(message)s' + }, + 'simple': { + 'format': '%(asctime)s %(levelname)s %(message)s' + }, + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'level': 'DEBUG', + 'formatter': 'simple', + }, + 'file': { + 'class': 'logging.handlers.RotatingFileHandler', + 'level': 'DEBUG', + 'formatter': 'simple', + 'filename': os.path.join(PROJECT_PATH, 'logs', 'madeira.log'), + 'mode': 'a', + 'maxBytes': 100 * 1024, + 'backupCount': 10, + }, + 'mail_admins': { + 'class': 'django.utils.log.AdminEmailHandler', + 'level': 'ERROR', + 'formatter': 'simple', + }, + }, + 'loggers': { + 'django':{ + 'level': 'WARNING', + 'propagate': False, + 'handlers': ['file'], + }, + 'django.request':{ + 'level': 'ERROR', + 'propagate': True, + 'handlers': ['mail_admins'], + }, + }, + 'root': { + 'level': 'INFO', + 'handlers': ['file'], + }, +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/404.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head><title>Page Not Found</title> +<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/theme.css" /> +<link rel="shortcut icon" type="image/vnd.microsoft.com" href="{{ STATIC_URL }}images/favicon.ico" /> +</head> +<body> + + <h1>Not Found</h1> + + <p>The requested URL {{ request.path|escape }} was not found on this server.</p> + +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/500.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head><title>Internal Server Error</title> +</head> +<body> + + <h1>Internal Server Error</h1> + + <p>We're sorry, that page is currently unavailable due to a server misconfiguration.</p> + <p>The server administrator has been notified, and we apologise for any inconvenience.</p> + +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/admin/band/email.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,34 @@ +{% extends 'admin/base_site.html' %} +{% load adminmedia %} +{% block title %}The Madeira | Mailing List Email Form{% endblock %} +{% block extrastyle %} +<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" /> +{% endblock %} +{% block bodyclass %}change-form{% endblock %} +{% block breadcrumbs %} +<div class="breadcrumbs"> + <a href="/admin">Home</a> +</div> +{% endblock %} +{% block content %} +<h1>Madeira Mailing List Email Form</h1> +<div id="content-main"> +<p>Use this form to send an email to all subscribers of the The Madeira mailing list.</p> +<form method="post" action="{{ request.build_absolute_uri }}">{% csrf_token %} +<div> +<fieldset class="module aligned"> + {% for field in form %} + <div class="form-row {% if field.field.required %}required{% endif %} {% if field.errors %}errors{% endif %}"> + {% if field.errors %}{{ field.errors }}{% endif %} + {{ field.label_tag }} + {{ field }} + </div> + {% endfor %} +</fieldset> +<div class="submit-row"> + <input type="submit" name="Send" value="Send" class="default" /> +</div> +</div> +</form> +</div> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/admin/band/email_sent.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,17 @@ +{% extends 'admin/base_site.html' %} +{% load adminmedia %} +{% block title %}The Madeira | Mailing List Email Sent{% endblock %} +{% block extrastyle %} +<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" /> +{% endblock %} +{% block breadcrumbs %} +<div class="breadcrumbs"> + <a href="/admin">Home</a> +</div> +{% endblock %} +{% block content %} +<h1>Madeira Mailing List Email Sent</h1> +<div id="content-main"> + <p>Your email has been sent.</p> +</div> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/admin/base_site.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,13 @@ +{% extends "admin/base.html" %} +{% load i18n %} + +{% block title %}{{ title }} | {% trans 'Madeira site admin' %}{% endblock %} +{% block extrahead %} +<link rel="shortcut icon" href="{{ STATIC_URL }}images/favicon.ico" type="image/vnd.microsoft.icon" /> +{% endblock %} + +{% block branding %} +<h1 id="site-name">{% trans 'Madeira site administration' %}</h1> +{% endblock %} + +{% block nav-global %}{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/admin/index.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,129 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css" />{% endblock %} + +{% block coltype %}colMS{% endblock %} +{% block bodyclass %}dashboard{% endblock %} +{% block breadcrumbs %}{% endblock %} +{% block content %} +<div id="content-main"> + +{% if app_list %} + <div class="module"> + <table summary="Madeira custom views"> + <caption>Madeira Quick Links</caption> + <!-- ============================================================================== --> + {% if perms.band.add_news or perms.band.change_news %} + <tr> + <th>{% if perms.band.change_news %}<a href="band/news/">{% endif %}News Items{% if perms.band.change_news %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_news %}<a href="band/news/add/" class="addlink">{% endif %}Add{% if perms.band.add_news %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_news %}<a href="band/news/" class="changelink">{% endif %}Change{% if perms.band.change_news %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_member or perms.band.change_member %} + <tr> + <th>{% if perms.band.change_member %}<a href="band/member/">{% endif %}Biography & Gear{% if perms.band.change_member %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_member %}<a href="band/member/add/" class="addlink">{% endif %}Add{% if perms.band.add_member %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_member %}<a href="band/member/" class="changelink">{% endif %}Change{% if perms.band.change_member %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_gig or perms.band.change_gig %} + <tr> + <th>{% if perms.band.change_gig %}<a href="band/gig/">{% endif %}Gigs{% if perms.band.change_gig %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_gig %}<a href="band/gig/add/" class="addlink">{% endif %}Add{% if perms.band.add_gig %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_gig %}<a href="band/gig/" class="changelink">{% endif %}Change{% if perms.band.change_gig %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_article or perms.band.change_article %} + <tr> + <th>{% if perms.band.change_article %}<a href="band/article/">{% endif %}Press Items{% if perms.band.change_article %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_article %}<a href="band/article/add/" class="addlink">{% endif %}Add{% if perms.band.add_article %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_article %}<a href="band/article/" class="changelink">{% endif %}Change{% if perms.band.change_article %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_mp3_set or perms.band.change_mp3_set %} + <tr> + <th>{% if perms.band.change_mp3_set %}<a href="band/mp3_set/">{% endif %}Songs{% if perms.band.change_mp3_set %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_mp3_set %}<a href="band/mp3_set/add/" class="addlink">{% endif %}Add{% if perms.band.add_mp3_set %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_mp3_set %}<a href="band/mp3_set/" class="changelink">{% endif %}Change{% if perms.band.change_mp3_set %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_video_set or perms.band.change_video_set %} + <tr> + <th>{% if perms.band.change_video_set %}<a href="band/video_set/">{% endif %}Videos{% if perms.band.change_video_set %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_video_set %}<a href="band/video_set/add/" class="addlink">{% endif %}Add{% if perms.band.add_video_set %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_video_set %}<a href="band/video_set/" class="changelink">{% endif %}Change{% if perms.band.change_video_set %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_merchandise or perms.band.change_merchandise %} + <tr> + <th>{% if perms.band.change_merchandise %}<a href="band/merchandise/">{% endif %}Merchandise{% if perms.band.change_merchandise %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_merchandise %}<a href="band/merchandise/add/" class="addlink">{% endif %}Add{% if perms.band.add_merchandise %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_merchandise %}<a href="band/merchandise/" class="changelink">{% endif %}Change{% if perms.band.change_merchandise %}</a>{% endif %}</td> + </tr> + {% endif %} + + + <!-- ============================================================================== --> + <tr><th scope="row"><a href="{% url band.admin_views.email %}">Send Email to Mailing List</a></th> + <td> </td> + <td> </td> + </tr> + </table> + </div> + + + {% for app in app_list %} + <div class="module"> + <table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}"> + <caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption> + {% for model in app.models %} + <tr> + {% if model.perms.change %} + <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th> + {% else %} + <th scope="row">{{ model.name }}</th> + {% endif %} + + {% if model.perms.add %} + <td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td> + {% else %} + <td> </td> + {% endif %} + + {% if model.perms.change %} + <td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td> + {% else %} + <td> </td> + {% endif %} + </tr> + {% endfor %} + </table> + </div> + {% endfor %} +{% else %} + <p>{% trans "You don't have permission to edit anything." %}</p> +{% endif %} +</div> +{% endblock %} + +{% block sidebar %} +<div id="content-related"> + <div class="module" id="recent-actions-module"> + <h2>{% trans 'Recent Actions' %}</h2> + <h3>{% trans 'My Actions' %}</h3> + {% load log %} + {% get_admin_log 10 as admin_log for_user user %} + {% if not admin_log %} + <p>{% trans 'None available' %}</p> + {% else %} + <ul class="actionlist"> + {% for entry in admin_log %} + <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span></li> + {% endfor %} + </ul> + {% endif %} + </div> +</div> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/base.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +{% load url from future %} +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head><title>{% block title %}{% endblock %}</title> +<meta http-equiv="Content-Type" content="text/html" /> +<meta http-equiv="Content-Language" content="en-US" /> +<meta name="robots" content="all" /> +<meta name="Author" content="Brian Neal" /> +<meta name="copyright" content="© 2007-2010 Brian Neal" /> +<meta name="keywords" lang="en-us" content="instrumental surf, surf, guitar, musician, instro, surf music, Dick Dale, Atlantics, Surf Coasters, Fender, Strat, Stratocaster, Destination Earth, Destination: Earth!,Space Cossacks, Troubadours, reverb" /> +<meta name="description" lang="en-us" content="Home page for the instrumental surf band The Madeira. The Madeira combine high energy performances reminiscent of The Atlantics and Dick Dale with exotic melodies and an unusually high level of musicianship. This page contains show dates, photos, videos, and news about the band." /> +<link rel="stylesheet" href="{{ STATIC_URL }}css/blueprint/screen.css" type="text/css" media="screen, projection" /> +<link rel="stylesheet" href="{{ STATIC_URL }}css/blueprint/print.css" type="text/css" media="print" /> +<!--[if lt IE 8]> +<link rel="stylesheet" href="{{ STATIC_URL }}css/blueprint/ie.css" type="text/css" media="screen, projection" /> +<![endif]--> +<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/theme.css" /> +{% block custom_css %}{% endblock %} +{% block custom_js %}{% endblock %} +<link rel="shortcut icon" type="image/vnd.microsoft.com" href="{{ STATIC_URL }}images/favicon.ico" /> +</head> +<body> +<div class="container"> + +<div id="header" class="span-24 last"> + <img src="{{ STATIC_URL }}images/header-logo.jpg" border="0" alt="Madeira Logo" /> +</div> + +<div id="navleft" class="span-4 append-1"> + <ul> + <li><a href="{% url 'band.views.index' %}">Home</a></li> + <li><a href="{% url 'band.views.news' %}">News</a></li> + <li><a href="{% url 'band.views.bio' %}">Biography</a></li> + <li><a href="{% url 'band.views.gigs' %}">Shows</a></li> + <li><a href="{% url 'band.views.press_index' %}">Press</a></li> + <li><a href="{% url 'band.views.songs' %}">Songs</a></li> + <li><a href="{% url 'band.views.photos_index' %}">Photos</a></li> + <li><a href="{% url 'band.views.videos_index' %}">Videos</a></li> + <li><a href="{% url 'band.views.flyers' %}">Flyers</a></li> + <li><a href="{% url 'band.views.buy' %}">Buy</a></li> + <li><a href="{% url 'band.views.contact' %}">Contact</a></li> + <li><a href="{% url 'band.views.mail' %}">Mailing List</a></li> + <li><a href="http://myspace.com/themadeira">Myspace</a></li> + <li><a href="http://facebook.com/themadeira">Facebook</a></li> + <li><a href="http://www.youtube.com/user/TheMadeiraSurf">YouTube</a></li> + </ul> +</div> + +<div id="xxx-content" class="span-19 last"> + {% block content %} + {% endblock %} +</div> + +<div id="footer" class="span-24 last"> +Website © 2008 - 2011 by The Madeira <br /> +Visit <a href="http://myspace.com/themadeira">The Madeira on Myspace</a><br /> +and <a href="http://facebook.com/themadeira">The Madeira on Facebook</a><br /> +</div> + +</div> +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/bio.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,26 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Biography{% endblock %} +{% block content %} +<h1>Band Biography</h1> +{% if members %} + {% for member in members %} + <h2>{{ member.name }} - {{ member.instrument }}</h2> + <p> + {% if member.photo %} + <img class="left" src="{{ member.photo.url }}" border="0" alt="{{ member.name }}" title="{{ member.name }}" /> + {{ member.bio|linebreaks }} + </p> + {% if member.gear_set.all %} + <p>Gear:</p> + <ul> + {% for item in member.gear_set.all %} + <li>{{ item.item }}</li> + {% endfor %} + </ul> + {% endif %} + {% endif %} + {% endfor %} +{% else %} +The band has no members. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/buy.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,52 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Merchandise{% endblock %} +{% block content %} +<h1>Madeira Merchandise</h1> +{% for album in albums %} + <h2>{{ album.title }} </h2> + <p> + <img class="right" src="{{ album.photo.image.url }}" alt="{{ album.title }}" title="{{ album.title }}" /> + </p> + {% if album.label_release_set %} + <ul> + {% for release in album.label_release_set.all %} + <li><a href="{{ release.record_label.url }}">{{ release.record_label.name }}</a> + {{ release.catalog_number }}, {{ release.release_date|date:"F d, Y" }}</li> + {% endfor %} + </ul> + {% endif %} + {{ album.desc|safe|linebreaks }} + <p>Track listing:</p> + <ol> + {% for track in album.album_track_set.all %} + <li>{{ track.track_name }}</li> + {% endfor %} + </ol> + {% if album.album_merchant_set %} + <p>Buy {{ album.title }} at:</p> + <ul> + {% for merchant in album.album_merchant_set.all %} + <li><a href="{{ merchant.url }}">{{ merchant.name }}</a></li> + {% endfor %} + </ul> + {% endif %} + <br clear="all" /> +{% endfor %} +{% if merchandise %} + <hr /> + {{ config.ordering_info|safe|linebreaks }} +{% endif %} +{% for item in merchandise %} + <h2>{{ item.name }}</h2> + <p> + <img class="right" src="{{ item.photo.image.url }}" alt="{{ item.name }}" title="{{ item.name }}" /> + </p> + {{ item.desc|safe|linebreaks }} + {% if item.in_stock %} + <p>Price: ${{ item.price }}</p> + {% else %} + <p><strike>Price: ${{ item.price }}</strike> <strong>SOLD OUT!</strong></p> + {% endif %} + <br clear="all" /> +{% endfor %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/contact.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,14 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Contact{% endblock %} +{% block content %} +<h1>Madeira Contact Info</h1> +<p>For general band inquiries, send email to: <a href="mailto:{{ config.contact_email }}">{{ config.contact_email }}</a>.</p> +<p>To contact individual band members:</p> +<ul> +{% for member in band %} + {% if member.email %} + <li>{{ member.name }}: <a href="mailto:{{ member.email }}">{{ member.email }}</a></li> + {% endif %} +{% endfor %} +</ul> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/email_subscribe.txt Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,19 @@ +Hello, + +We have received a request for this email address to join the mailing list +for {{ band }}. In order for us to process this subscription, we need confirmation from you. + +If you did not request to join this mailing list, you may ignore this message. + +To subscribe to the mailing list, go to the following confirmation URL: + +{{ url }} + +This should take you directly to an email confirmation page. If it does not, +please copy and paste the full URL into your web browser's address box and +hit the "Enter" key on your keyboard. + +Thanks, + +{{ band }} +{{ band_url }}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/email_unsubscribe.txt Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,19 @@ +Hello, + +We have received a request for this email address to unsubscribe from the mailing list +for {{ band }}. In order for us to process this request, we need confirmation from you. + +If you did not request to unsubscribe from this mailing list, you may ignore this message. + +To unsubscribe from the mailing list, go to the following confirmation URL: + +{{ url }} + +This should take you directly to an email confirmation page. If it does not, +please copy and paste the full URL into your web browser's address box and +hit the "Enter" key on your keyboard. + +Thanks, + +{{ band }} +{{ band_url }}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/flyers.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,20 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Flyer Gallery{% endblock %} +{% block content %} +<h1>Show Flyer Gallery</h1> +{% if gigs %} + <center> + {% for gig in gigs %} + <p> + {% if gig.title %} + <img src="{{ gig.flyer.image.url }}" alt="{{ gig.title }}" title="{{ gig.title }} : {{ gig.date|date:"F d, Y" }}" /> + {% else %} + <img src="{{ gig.flyer.image.url }}" alt="{{ gig.date|date:"F d, Y" }}" title="{{ gig.date|date:"F d, Y" }}" /> + {% endif %} + </p> + {% endfor %} + </center> +{% else %} +No flyers available at this time. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/gigs.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,166 @@ +{% extends 'band/base.html' %} +{% load url from future %} +{% block title %}The Madeira | Shows{% endblock %} +{% block custom_css %} +<link rel="stylesheet" href="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" /> +{% endblock %} +{% block custom_js %} +<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> +<script type="text/javascript" src="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.pack.js"></script> +<script type="text/javascript"> +$(function() { + $('a.fancybox').fancybox(); +}); +</script> +{% endblock %} +{% block content %} +<h1>Show Dates</h1> + +<h2>Upcoming Shows</h2> +{% if upcoming %} + {% for show in upcoming %} + <p style="clear:both"> + {% if show.flyer %} + <a href="{{ show.flyer.image.url }}" class="fancybox" rel="madeira-gallery"> + <!-- <img style="float:left; margin-right:5px; margin-bottom:1em" src="{{ show.flyer.get_thumbnail_url }}" --> + <img class="left" src="{{ show.flyer.get_thumbnail_url }}" + alt="{{ show.flyer.caption }}" title="{{ show.flyer.caption }}" /></a> + {% endif %} + <strong>{{ show.date|date:"F d, Y" }}</strong> + {% if show.time %}{{ show.time|time:"h:i A" }}{% endif %}<br /> + + {% if show.title and show.url %} + <a href="{{ show.url }}" target="_blank">{{ show.title }}</a><br /> + {% else %} + {% if show.title %} + {{ show.title }}<br /> + {% endif %} + {% endif %} + + {% if show.venue %} + {% if show.venue.url %} + <a href="{{ show.venue.url }}" target="_blank">{{ show.venue.name }}</a>, + {% else %} + {{ show.venue }}, + {% endif %} + {% if show.venue.address %} + {{ show.venue.address }}, + {% endif %} + {% if show.venue.city.state %} + {{ show.venue.city.name }}, {{ show.venue.city.state.name }} + {% else %} + {{ show.venue.city.name }} + {% endif %} + {% ifnotequal show.venue.city.country.name "USA" %} + {{ show.venue.city.country.name }} + {% endifnotequal %} + <br /> + {% if show.venue.phone %} + {{ show.venue.phone }} + <br /> + {% endif %} + {% endif %} + + {% if show.bands_ %} + With: + {% for band in show.bands_ %} + {% if band.url %} + <a href="{{ band.url }}" target="_blank">{{ band.name }}</a> + {% else %} + {{ band.name }} + {% endif %} + {% if not forloop.last %} + • + {% endif %} + {% endfor %} + <br /> + {% endif %} + + {% if show.notes %} + {{ show.notes|safe }} + {% endif %} + </p> + {% endfor %} +{% else %} +None at this time. +{% endif %} +<br clear="all" /> + +{% if flyerGigs %} +<div class="thumb-box"> + <h2>Flyers</h2> + <div style="width:90%; margin-left:auto;"> + {% for gig in flyerGigs %} + <div style="display:inline-table;"> + <table class="image-table"> + <caption>{{ gig.venue.name}}, {{ gig.date|date:"F 'y" }}</caption> + <tr><td> + <a href="{{ gig.flyer.image.url }}" class="fancybox" rel="madeira-gallery"> + <img src="{{ gig.flyer.get_thumbnail_url }}" alt="{{ gig.date|date:"F d, Y" }}" title="{{ gig.date|date:"F d, Y" }}" /></a> + </td></tr> + </table> + </div> + {% endfor %} + </div> + <div clear="all"></div> + <center><p>To see all our flyers in full size, check out our <a href="{% url 'band.views.flyers' %}">show flyer gallery</a>.</p></center> +</div> +{% endif %} + +{% if previous %} + <h2>Previous Shows</h2> + <center> + <table border="0" cellpadding="3" cellspacing="3" width="95%"> + <tr><th width="20%" align="center">Date</th><th width="40%" align="center">Venue</th><th width="40%" align="center">Bands</th></tr> + {% for show in previous %} + <tr> + <td width="20%">{{ show.date|date:"M d, Y" }}</td> + <td width="40%"> + {% if show.title and show.url %} + <a href="{{ show.url }}" target="_blank">{{ show.title }}</a>, + {% else %} + {% if show.title %} + {{ show.title }}, + {% endif %} + {% endif %} + {% if show.venue.url %} + <a href="{{ show.venue.url }}" target="_blank">{{ show.venue.name }}</a>, + {% else %} + {{ show.venue.name }}, + {% endif %} + {{ show.venue.city.name }}, {{ show.venue.city.state.abbrev }} + {% ifnotequal show.venue.city.country.name "USA" %} + {{ show.venue.city.country.name }} + {% endifnotequal %} + </td> + <td width="40%"> + {% for band in show.bands_ %} + {% if band.url %} + <a href="{{ band.url }}" target="_blank">{{ band.name }}</a> + {% else %} + {{ band.name }} + {% endif %} + {% if not forloop.last %} + • + {% endif %} + {% endfor %} + </td> + </tr> + {% endfor %} + </table> + </center> +{% endif %} + +{% if stats %} +<h2>Past Show Statistics</h2> +<table border="0" cellpadding="3" cellspacing="3"> + <tr><th align="left">Number of shows:</th><td>{{ stats.count }}</td></tr> + <tr><th align="left">Number of unique venues:</th><td>{{ stats.venues }}</td></tr> + <tr><th align="left">Number of unique cities:</th><td>{{ stats.cities }}</td></tr> + <tr><th align="left">Number of unique states:</th><td>{{ stats.states }}</td></tr> + <tr><th align="left">Number of unique countries:</th><td>{{ stats.countries }}</td></tr> + <tr><th align="left">Number of unique bands:</th><td>{{ stats.bands }}</td></tr> +</table> +{% endif %} + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/index.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,90 @@ +{% extends 'band/base.html' %} +{% load url from future %} +{% block title %}The Madeira{% endblock %} +{% block custom_css %} +<link rel="stylesheet" href="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" /> +{% endblock %} +{% block custom_js %} +<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> +<script type="text/javascript" src="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.pack.js"></script> +<script type="text/javascript"> +$(function() { + $('a.fancybox').fancybox(); +}); +</script> +{% endblock %} +{% load markup %} +{% block content %} +<h1>The Madeira</h1> +<img class="floatLeftBox" src="{{ config.intro_photo.image.url }}" + alt="{{ config.intro_photo.title }}" title="{{config.intro_photo.title}}" border="0" /> +{{ config.intro_text|textile }} +<br /> + +{% if upcomingDates %} +<div class="center-block"> +<h2>Upcoming Shows...</h2> + +<center><table border="0" cellspacing="10" cellpadding="3"><tr> +{% for gig in upcomingDates %} + {% if gig.flyer %} + <td> + <a href="{{ gig.flyer.image.url }}" class="fancybox" rel="madeira-gallery"> + <img src="{{ gig.flyer.get_thumbnail_url }}" alt="{{ gig.flyer.caption }}" title="{{ gig.flyer.caption }}" /></a> + <br /><center>{{ gig.flyer.caption }}</center> + </td> + {% endif %} +{% endfor %} +</tr></table></center> + +<ul> +{% for show in upcomingDates %} +<li><strong>{{ show.date|date:"l, F d" }}</strong>: {{ show.venue.name }}, {{ show.venue.city.name }}{% if show.venue.city.state %}, {{ show.venue.city.state.name }} +{% endif %} +{% ifnotequal show.venue.city.country.name "USA" %} +{{ show.venue.city.country.name }} +{% endifnotequal %} +</li> +{% endfor %} +</ul> +<center><a href="{% url 'band.views.gigs' %}">See all upcoming shows...</a></center> +</div> +{% endif %} + +<div> + <center> + <h2>Sandstorm from Sound of the Surf</h2> +<object width="640" height="390"><param name="movie" value="http://www.youtube-nocookie.com/v/IFnyaCPyJSk?fs=1&hl=en_US&rel=0"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube-nocookie.com/v/IFnyaCPyJSk?fs=1&hl=en_US&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="390"></embed></object> +<p>Another clip from the upcoming film <a href="http://soundofthesurf.com">Sound of the Surf</a> has just been released, and it is our performance of Sandstorm! This movie cannot come out soon enough!</p> +</center> +</div> +<div> + <center> + <h2>New Song Preview!</h2> +<object width="480" height="385"><param name="movie" value="http://www.youtube.com/p/B67E923C98CCECD8?hl=en_US&fs=1"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/p/B67E923C98CCECD8?hl=en_US&fs=1" type="application/x-shockwave-flash" width="480" height="385" allowscriptaccess="always" allowfullscreen="true"></embed></object> + <p>Check out this set of 6 videos from our show at Mahogany's in February. Five of the songs are new originals slated for our new album! Video courtesy of TikiTim.</p> + </center> +</div> + +<div class="newsflash"> + <center> + <table border="0" cellspacing="2" cellpadding="2" width="100%"> + <tr><td colspan="2"><center><h1>The Madeira Releases:</h1></center></td></tr> + + <tr><td colspan="2"><center> + <img src="{{ carpe.image.url }}" alt="Carpe Noctem Cover" title="Carpe Noctem" border="0" /> + </center></td></tr> + <tr><td colspan="2"><center><strong>Available Now: Carpe Noctem!</strong><br /> + </center></td></tr> + <tr><td><center><a href="http://www.dblcrown.com"><img src="{{ sandstorm.image.url }}" alt="Sandstorm CD Cover" + title="Sandstorm" border="0" /></a></center></td> + + <td><center><a href="http://www.dblcrown.com"><img src="{{ ruins.image.url }}" alt="Ruins EP Cover" title="Ruins" + border="0" /></a></center></td></tr> + <tr><td><center><a href="http://www.dblcrown.com">Sandstorm</a></center></td> + <td><center><a href="http://www.dblcrown.com">Ruins</a></center></td></tr> + </table> + </center> +</div> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/mail.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,21 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Mailing List{% endblock %} +{% block content %} +<h1>Madeira Mailing List</h1> +<p>Get on the Madeira mailing list to receive updates about upcoming shows, releases, and website updates. +This is a low volume list. We do not share your email address with anyone.</p> +<fieldset><legend>Mailing List</legend> + <form method="post" action="{{ request.build_absolute_uri }}">{% csrf_token %} + <table border="0" class="input-form"> + {% for field in form %} + <tr> + <th>{{ field.label_tag }}{% if field.field.required %}*{% endif %}:</th> + <td>{{ field }} + {% if field.errors %}{{ field.errors }}{% endif %}</td> + </tr> + {% endfor %} + <tr><td><input type="submit" name="Submit" value="Submit" class="submit-button" /></td></tr> + </table> + </form> +</fieldset> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/mail_confirm.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,6 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Mailing List Confirmation{% endblock %} +{% block content %} +<h1>Madeira Mailing List Confirmation</h1> +<p>Your email address, {{ email }}, has been successfully {{ action }}.</p> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/mail_not_found.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,8 @@ +{% extends 'band/base.html' %} +{% load url from future %} +{% block title %}The Madeira | Mailing List Confirmation{% endblock %} +{% block content %} +<h1>Madeira Mailing List</h1> +<p>Sorry, we did not find that email address in our database.</p> +<p>Back to <a href="{% url 'band.views.contact' %}">contact page</a>.</p> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/mail_thanks.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,9 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Mailing List Confirmation{% endblock %} +{% block content %} +<h1>Madeira Mailing List</h1> +<p>Thanks for subscribing to our email list! You should shortly receive a confirmation email +with instructions on how to complete the subscription process.</p> +<p><strong>Please check your spam folders for this email</strong>. Sometimes it ends up in there. +Thanks.</p> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/mail_unsubscribe.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,8 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Mailing List Confirmation{% endblock %} +{% block content %} +<h1>Madeira Mailing List</h1> +<p>We're sorry to see you unsubscribing from our email list! You should shortly receive a confirmation email +with instructions on how to complete the removal process. <strong>Please check your spam folders for +this email</strong>.</p> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/news.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,31 @@ +{% extends 'band/base.html' %} +{% load markup %} +{% block title %}The Madeira | News{% endblock %} +{% block content %} +<h1>News</h1> +{% if news %} + {% for story in news %} + <h2>{{ story.date|date:"F d, Y" }} + {% if story.title %} + • {{ story.title }} + {% endif %} + </h2> + <div> + {% if story.photo %} + <img src="{{ story.photo.url }}" class="floatLeftBox" + alt="{{ story.photo_caption }}" title="{{ story.photo_caption }}" border="0" /> + {% endif %} + {% if story.markup_enabled %} + {{ story.text|textile }} + {% else %} + {{ story.text|safe|linebreaks }} + {% endif %} + {% if story.author %} + <p><em>-- {{ story.author }}</em></p> + {% endif %} + </div> + {% endfor %} +{% else %} +No news at this time. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/photo_detail.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,29 @@ +{% extends 'band/base.html' %} +{% load url from future %} +{% load markup %} +{% block title %}The Madeira | Photos: {{ gallery.title }}{% endblock %} +{% block custom_css %} +<link rel="stylesheet" href="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" /> +{% endblock %} +{% block custom_js %} +<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> +<script type="text/javascript" src="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.pack.js"></script> +<script type="text/javascript"> +$(function() { + $('a.fancybox').fancybox(); +}); +</script> +{% endblock %} +{% block content %} +<h1>Madeira Photos: {{ gallery.title }}</h1> +{{ gallery.description|textile }} + +<div class="madeira-photo-list"> +{% for photo in photos %} + <a href="{{ photo.image.url }}" class="fancybox" rel="madeira-gallery"> + <img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.caption }}" title="{{ photo.caption }}" /></a> +{% endfor %} +</div> +<center><a href="{% url 'band.views.photos_index' %}">Photo gallery index</a></center> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/photos.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,36 @@ +{% extends 'band/base.html' %} +{% load url from future %} +{% block title %}The Madeira | Photo Galleries{% endblock %} +{% block custom_css %} +<link rel="stylesheet" href="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" /> +{% endblock %} +{% block custom_js %} +<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> +<script type="text/javascript" src="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.pack.js"></script> +<script type="text/javascript"> +$(function() { + $('a.fancybox').fancybox(); +}); +</script> +{% endblock %} +{% block content %} +<h1>Madeira Photo Galleries</h1> +{% if galleries %} + <ul> + {% for gallery in galleries %} + <li><a href="{% url 'band.views.photo_detail' gallery.id %}">{{ gallery.title }}</a></li> + {% endfor %} + </ul> +{% else %} +No photo galleries available at this time. +{% endif %} +{% if randomPhotos %} + <div class="madeira-photo-list"> + <h2>Random Photos:</h2> + {% for photo in randomPhotos %} + <a href="{{ photo.image.url }}" class="fancybox" rel="madeira-gallery"> + <img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.caption }}" title="{{ photo.caption }}" /></a> + {% endfor %} + </div> +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/press.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,44 @@ +{% extends 'band/base.html' %} +{% load markup %} +{% block title %}The Madeira | Press{% endblock %} +{% block content %} +<h1>Madeira Press, Articles, & Reviews</h1> +{% if articles %} + <a name="Contents"> </a> + <h2>Contents</h2> + <ul> + {% for article in articles %} + <li><a href="#article_{{ article.id }}">{{ article.title }}</a></li> + {% endfor %} + </ul> + + {% for article in articles %} + <a name="article_{{ article.id }}"> </a> + <h2>{{ article.title }}</h2> + {% if article.markup_enabled %} + {{ article.text|textile }} + {% else %} + {{ article.text|safe|linebreaks }} + {% endif %} + <div class="article-source"> + {{ article.source|safe|linebreaks }} + </div> + {% if article.url %} + <a href="{{ article.url }}" target="_blank">Original article</a> + {% endif %} + {% if article.pdf and article.url %} + | + {% endif %} + {% if article.pdf %} + <a href="{{ article.pdf.url }}" target="_blank">Original article as PDF</a> + <a href="http://www.adobe.com/products/acrobat/readstep2.html"> + <img src="{{ STATIC_URL }}images/get_adobe_reader.gif" alt="Adobe Reader" title="Get Adobe Reader" border="0" + align="middle" /></a> + {% endif %} + <p><a class="intLink" href="#Contents">Top</a></p> + {% endfor %} + +{% else %} +No articles at this time. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/press_detail.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,29 @@ +{% extends 'band/base.html' %} +{% load url from future %} +{% load markup %} +{% block title %}The Madeira | Press{% endblock %} +{% block content %} +<h1>Madeira Press, Articles, & Reviews</h1> +<h2>{{ article.title }}</h2> +{% if article.markup_enabled %} + {{ article.text|textile }} +{% else %} + {{ article.text|safe|linebreaks }} +{% endif %} +<div class="article-source"> +{{ article.source|safe|linebreaks }} +</div> +{% if article.url %} +<a href="{{ article.url }}" target="_blank">Original article</a> +{% endif %} +{% if article.pdf and article.url %} +| +{% endif %} +{% if article.pdf %} +<a href="{{ article.get_pdf_url }}" target="_blank">Original article as PDF</a> +<a href="http://www.adobe.com/products/acrobat/readstep2.html"> + <img src="{{ STATIC_URL }}images/get_adobe_reader.gif" alt="Adobe Reader" title="Get Adobe Reader" border="0" + align="middle" /></a> +{% endif %} +<p><a href="{% url 'band.views.press_index' %}">Press index</a></p> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/songs.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,20 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Songs{% endblock %} +{% block content %} +<h1>Madeira Songs</h1> +{% if mp3Sets %} + <p>Check out some Madeira MP3 downloads!</p> + {% for set in mp3Sets %} + <h2>{{ set.title }}</h2> + {{ set.text|safe|linebreaks }} + <ul> + {% for mp3 in set.mp3_set.all %} + <li><a href="{{ mp3.file.url }}">{{ mp3.title }}</a> + ({{ mp3.file.size|filesizeformat }}){% if mp3.desc %} - {{ mp3.desc }}{% endif %}</li> + {% endfor %} + </ul> + {% endfor %} +{% else %} +No downloads available at this time. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/video_detail.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,18 @@ +{% extends 'band/base.html' %} +{% load url from future %} +{% block title %}The Madeira | Videos: {{ vidset.title }}{% endblock %} +{% block content %} +<h1>Madeira Videos: {{ vidset.title }}</h1> +{{ vidset.text|safe|linebreaks }} + +<div align="center"> +<table cellspacing="3" cellpadding="2" border="0"> +{% for video in vidset.video_set.all %} + <tr><th>{{ video.title }}</th><td>{{ video.embed_code|safe }}</td></tr> +{% endfor %} +</table> +</div> +<br /> +<center><a href="{% url 'band.views.videos_index' %}">Videos index</a></center> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/band/videos.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,15 @@ +{% extends 'band/base.html' %} +{% load url from future %} +{% block title %}The Madeira | Videos{% endblock %} +{% block content %} +<h1>Madeira Videos</h1> +{% if vidsets %} + <ul> + {% for set in vidsets %} + <li><a href="{% url 'band.views.video_detail' set.id %}">{{ set.title }}</a></li> + {% endfor %} + </ul> +{% else %} +No videos available at this time. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/templates/flatpages/default.html Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,5 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | {{ flatpage.title }}{% endblock %} +{% block content %} +{{ flatpage.content }} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/madeira/urls.py Tue Feb 14 19:15:16 2012 -0600 @@ -0,0 +1,17 @@ +from django.conf.urls.defaults import * +from django.contrib import admin +from django.conf import settings + +admin.autodiscover() + +urlpatterns = patterns('', + (r'^', include('band.urls')), + (r'^admin/doc/', include('django.contrib.admindocs.urls')), + (r'^admin/', include(admin.site.urls)), + (r'^photologue/', include('photologue.urls')), +) + +if settings.DEBUG: + urlpatterns += patterns('', + (r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}), + )
--- a/mysite/apache/madeira.wsgi Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -import os -import sys - -OFFLINE = False - -sys.path.append('/home/var/django-sites/madeira/django-trunk') -sys.path.append('/home/var/django-sites/madeira/madeira-trunk') -sys.path.append('/home/var/django-sites/madeira/madeira-trunk/mysite') -os.environ['PYTHON_EGG_CACHE'] = '/home/var/django-sites/madeira/eggs/' - - -def offline_handler(environ, start_response): - wsgi_dir = os.path.dirname(__file__) - sys.path.append(wsgi_dir) - - offline_file = os.path.join(wsgi_dir, '..', 'templates', 'offline.html') - if os.path.exists(offline_file): - response_headers = [('Content-type','text/html')] - response = open(offline_file).read() - else: - response_headers = [('Content-type','text/plain')] - response = 'themadeira.net website maintenance in progress; please check back soon.' - - if environ['REQUEST_METHOD'] == 'GET': - status = '503 Service Unavailable' - else: - status = '405 Method Not Allowed' - start_response(status, response_headers) - return [response] - - -if not OFFLINE: - os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings.production' - import django.core.handlers.wsgi - application = django.core.handlers.wsgi.WSGIHandler() -else: - application = offline_handler
--- a/mysite/band/admin.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,244 +0,0 @@ -####################################################################### -# -# PyBand Copyright (C) 2008 by Brian Neal -# -####################################################################### - -from django.contrib import admin - -from band.models import Article -from band.models import Album -from band.models import Album_Merchant -from band.models import Album_Track -from band.models import Band -from band.models import City -from band.models import Country -from band.models import Fan -from band.models import Gear -from band.models import Gig -from band.models import Label_Release -from band.models import Member -from band.models import Merchandise -from band.models import Mp3 -from band.models import Mp3_Set -from band.models import News -from band.models import Record_Label -from band.models import SiteConfig -from band.models import State -from band.models import Venue -from band.models import Video -from band.models import Video_Set - -####################################################################### - -admin.site.register(Video) - -####################################################################### - -class SiteConfigAdmin(admin.ModelAdmin): - list_display = ('band_name', 'url', 'contact_email') - fieldsets = ( - (None, { 'fields' : ('band_name', 'url', 'contact_email', 'intro_text', 'ordering_info', - 'intro_photo') }), - ) - -admin.site.register(SiteConfig, SiteConfigAdmin) - -####################################################################### - -class GearInline(admin.TabularInline): - model = Gear - -class GearAdmin(admin.ModelAdmin): - list_display = ('item', 'member') - list_filter = ('member', ) - -admin.site.register(Gear, GearAdmin) - -####################################################################### - -class MemberAdmin(admin.ModelAdmin): - list_display = ('name', 'instrument', 'is_active') - inlines = [ - GearInline, - ] - -admin.site.register(Member, MemberAdmin) - -####################################################################### - -class CityInline(admin.TabularInline): - model = City - -class CityAdmin(admin.ModelAdmin): - list_display = ('name', 'state', 'country') - list_filter = ('state', ) - search_fields = ('name', ) - -admin.site.register(City, CityAdmin) - -####################################################################### - -admin.site.register(Country) - -####################################################################### - -class StateAdmin(admin.ModelAdmin): - inlines = [ - CityInline, - ] - -admin.site.register(State, StateAdmin) - -####################################################################### - -class VenueAdmin(admin.ModelAdmin): - list_filter = ('city', ) - list_display = ('name', 'city', ) - search_fields = ('name', ) - -admin.site.register(Venue, VenueAdmin) - -####################################################################### - -class BandAdmin(admin.ModelAdmin): - search_fields = ('name', ) - -admin.site.register(Band, BandAdmin) - -####################################################################### - -class GigAdmin(admin.ModelAdmin): - list_filter = ('date', 'venue') - save_on_top = True - filter_horizontal = ('bands', ) - -admin.site.register(Gig, GigAdmin) - -####################################################################### - -class NewsAdmin(admin.ModelAdmin): - save_on_top = True - list_filter = ('date', ) - list_display = ('date', 'title') - search_fields = ('text', 'title') - -admin.site.register(News, NewsAdmin) - -####################################################################### - -class ArticleAdmin(admin.ModelAdmin): - save_on_top = True - list_filter = ('date', ) - list_display = ('title', 'date') - search_fields = ('text', 'title') - -admin.site.register(Article, ArticleAdmin) - -####################################################################### - -class Mp3Inline(admin.TabularInline): - model = Mp3 - -class Mp3Admin(admin.ModelAdmin): - prepopulated_fields = {'slug' : ('title', 'desc')} - -admin.site.register(Mp3, Mp3Admin) - -####################################################################### - -class Mp3_SetAdmin(admin.ModelAdmin): - list_filter = ('date', ) - list_display = ('title', 'date') - inlines = [ - Mp3Inline, - ] - -admin.site.register(Mp3_Set, Mp3_SetAdmin) - -####################################################################### - -class VideoInline(admin.TabularInline): - model = Video - -class Video_SetAdmin(admin.ModelAdmin): - list_filter = ('date', ) - list_display = ('title', 'date') - inlines = [ - VideoInline, - ] - -admin.site.register(Video_Set, Video_SetAdmin) - -####################################################################### - -class Album_TrackInline(admin.TabularInline): - model = Album_Track - -class Album_TrackAdmin(admin.ModelAdmin): - list_display = ('track_name', 'album') - list_filter = ('album', ) - -admin.site.register(Album_Track, Album_TrackAdmin) - -####################################################################### - -class Label_ReleaseInline(admin.TabularInline): - model = Label_Release - -class Label_ReleaseAdmin(admin.ModelAdmin): - list_display = ('catalog_number', 'album', 'record_label', 'release_date') - list_filter = ('record_label', 'album') - -admin.site.register(Label_Release, Label_ReleaseAdmin) - -####################################################################### - -class Record_LabelAdmin(admin.ModelAdmin): - inlines = [ - Label_ReleaseInline, - ] - -admin.site.register(Record_Label, Record_LabelAdmin) - -####################################################################### - -class Album_MerchantInline(admin.TabularInline): - model = Album_Merchant - -class Album_MerchantAdmin(admin.ModelAdmin): - list_display = ('name', 'album') - list_filter = ('album', ) - -admin.site.register(Album_Merchant, Album_MerchantAdmin) - -####################################################################### - -class AlbumAdmin(admin.ModelAdmin): - save_on_top = True - inlines = [ - Album_TrackInline, - Label_ReleaseInline, - Album_MerchantInline, - ] - -admin.site.register(Album, AlbumAdmin) - -####################################################################### - -class MerchandiseAdmin(admin.ModelAdmin): - list_display = ('name', 'price', 'in_stock') - list_filter = ('in_stock', ) - -admin.site.register(Merchandise, MerchandiseAdmin) - -####################################################################### - -class FanAdmin(admin.ModelAdmin): - list_display = ('name', 'email', 'current_status') - search_fields = ('name', 'email') - -admin.site.register(Fan, FanAdmin) - -####################################################################### -
--- a/mysite/band/admin_views.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -####################################################################### -# -# PyBand Copyright (C) 2008 by Brian Neal -# -####################################################################### -from django import forms -from django.core.urlresolvers import reverse -from django.core.mail import EmailMessage -from django.template import RequestContext -from django.http import HttpResponseRedirect -from django.shortcuts import render_to_response -from django.contrib.admin.views.decorators import staff_member_required - -from band.models import SiteConfig -from band.models import Fan - -####################################################################### - -unsubscribeText = ''' - ----- -You are receiving this message because you are subscribed to our mailing list. -If you would like to unsubscribe please visit %s. -''' - -####################################################################### - -class EmailForm(forms.Form): - subject = forms.CharField(max_length = 255, required = True, label = 'Subject:', - widget = forms.TextInput(attrs = {'class' : 'vTextField required', 'size' : '60'})) - message = forms.CharField(label = 'Message:', - widget = forms.Textarea(attrs = {'class' : 'vLargeTextField required'})) - -####################################################################### - -def email_sent(request): - return render_to_response('admin/band/email_sent.html', - {}, - context_instance = RequestContext(request)) - -####################################################################### - -def email(request): - - config = SiteConfig.objects.get(pk = 1) - bandTag = '[%s] ' % (config.band_name, ) - - if request.method == 'POST': - form = EmailForm(request.POST) - if form.is_valid(): - subject = form.cleaned_data['subject'] - message = form.cleaned_data['message'] - - unsubscribeUrl = config.url - if unsubscribeUrl[-1] != '/': - unsubscribeUrl += '/' - unsubscribeUrl += 'mail' - - footer = unsubscribeText % (unsubscribeUrl, ) - message += footer - - fans = Fan.objects.all() - bcc = [fan.email for fan in fans] - - email = EmailMessage(subject, message, config.contact_email, - [config.contact_email], bcc) - email.send() - return HttpResponseRedirect(reverse(email_sent)) - - else: - form = EmailForm(initial = { 'subject' : bandTag }) - - return render_to_response('admin/band/email.html', - { 'form' : form }, - context_instance = RequestContext(request)) - -email = staff_member_required(email)
--- a/mysite/band/models.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,393 +0,0 @@ -from django.db import models -from django.contrib.localflavor.us.models import USStateField -from django.contrib.localflavor.us.models import PhoneNumberField - -from photologue.models import Photo -import datetime -import random -import string - -####################################################################### - -class SiteConfig(models.Model): - band_name = models.CharField(max_length = 50) - url = models.URLField(verify_exists = False, max_length = 200) - contact_email = models.EmailField() - ordering_info = models.TextField(help_text = 'Enter instructions on how to order merchandise here') - intro_text = models.TextField(help_text = 'This text appears on the home page.') - intro_photo = models.ForeignKey(Photo) - - def __unicode__(self): - return self.band_name - - class Meta: - verbose_name = "Site Configuration" - verbose_name_plural = "Site Configuration" - -####################################################################### - -class Member(models.Model): - name = models.CharField(max_length = 50, db_index = True) - nickname = models.CharField(max_length = 50, blank = True) - instrument = models.CharField(max_length = 255) - bio = models.TextField(blank = True) - photo = models.FileField(upload_to = 'images/bio/', blank = True) - order = models.SmallIntegerField(help_text = '''Controls order of display on the bio page, lower numbers displayed - first''') - is_active = models.BooleanField(db_index = True) - start_date = models.DateField() - end_date = models.DateField(blank = True, help_text = 'Only used if the member is not active', - default = datetime.date(1985, 1, 1)) - email = models.EmailField() - - def __unicode__(self): - return self.name - - class Meta: - ordering = ('-is_active', 'name') - -####################################################################### - -class Gear(models.Model): - member = models.ForeignKey(Member) - item = models.CharField(max_length = 255) - - def __unicode__(self): - return self.item - - class Meta: - verbose_name_plural = 'Gear List' - -####################################################################### - -class Country(models.Model): - name = models.CharField(max_length=64) - - class Meta: - ordering = ('name', ) - verbose_name_plural = 'Countries' - - def __unicode__(self): - return self.name - -####################################################################### - -class State(models.Model): - name = models.CharField(max_length = 16) - abbrev = USStateField() - - class Meta: - ordering = ('name', ) - - def __unicode__(self): - return self.name - -####################################################################### - -class City(models.Model): - name = models.CharField(max_length = 50) - state = models.ForeignKey(State, null = True, blank = True) - country = models.ForeignKey(Country, null=True, blank=True) - - class Meta: - verbose_name_plural = 'Cities' - ordering = ('name', ) - - def __unicode__(self): - if self.state: - return self.name + u', ' + self.state.abbrev - return self.name - -####################################################################### - -class Venue(models.Model): - name = models.CharField(max_length = 50, db_index = True) - url = models.URLField(verify_exists = False, blank = True) - address = models.CharField(max_length = 255, blank = True) - phone = PhoneNumberField(help_text = "Format: XXX-XXX-XXXX", blank = True) - city = models.ForeignKey(City) - - class Meta: - ordering = ('name', ) - - def __unicode__(self): - return self.name - -####################################################################### - -class Band(models.Model): - name = models.CharField(max_length = 64) - url = models.URLField(verify_exists = False, blank = True) - - class Meta: - ordering = ('name', ) - - def __unicode__(self): - return self.name - -####################################################################### - -class Gig(models.Model): - title = models.CharField(max_length = 50, blank = True, help_text = "Optional; e.g. Some Festival") - url = models.URLField(verify_exists = False, blank = True, help_text = "Optional; e.g. Some Festival's Website") - date = models.DateField(db_index = True) - time = models.TimeField(null = True, blank = True) - venue = models.ForeignKey(Venue, null = True, blank = True) - notes = models.TextField(blank = True) - bands = models.ManyToManyField(Band, blank = True) - flyer = models.ForeignKey(Photo, null = True, blank = True) - - def __unicode__(self): - if self.title: - return u'%s %s %s' % (self.date.strftime('%m/%d/%Y'), self.title, self.venue.name) - elif self.venue: - return u'%s %s' % (self.date.strftime('%m/%d/%Y'), self.venue.name) - else: - return u'' + self.date.strftime('%m/%d/%Y') - - class Meta: - ordering = ('-date', 'time') - -####################################################################### - -class News(models.Model): - title = models.CharField(max_length = 64, blank = True) - date = models.DateField(db_index = True) - author = models.CharField(max_length = 50, blank = True) - text = models.TextField() - markup_enabled = models.BooleanField(default = True, - help_text = 'Check this box to allow Textile style markup in the text field') - photo = models.FileField(upload_to = 'images/news/%Y/%m/%d/', blank = True) - photo_caption = models.CharField(max_length = 50, blank = True) - - def __unicode__(self): - return u'%s %s' % (self.date.strftime('%m/%d/%Y'), self.title) - - class Meta: - ordering = ('-date', ) - verbose_name_plural = "News" - -####################################################################### - -class Article(models.Model): - title = models.CharField(max_length = 64) - date = models.DateField(db_index = True) - text = models.TextField() - markup_enabled = models.BooleanField(default = True, - help_text = 'Check this box to allow Textile style markup in the text field') - source = models.TextField(help_text = '''Enter the source/author for the article, copyright info, etc; it will appear under - the article.''') - url = models.URLField(blank = True, help_text = 'Link to original article; optional') - pdf = models.FileField(upload_to = 'pdf/articles/%Y/%m/%d/', blank = True, - help_text = '''If you want to make the original article available as a PDF download, you may upload it - here.''') - - def __unicode__(self): - return self.title - - class Meta: - ordering = ('date', ) - -####################################################################### - -class Mp3_Set(models.Model): - date = models.DateField(auto_now_add = True, editable = False) - title = models.CharField(max_length = 64) - text = models.TextField() - - def __unicode__(self): - return self.title - - class Meta: - ordering = ('date', ) - verbose_name = "MP3 Set" - -####################################################################### - -class Mp3(models.Model): - mp3_set = models.ForeignKey(Mp3_Set) - title = models.CharField(max_length = 64) - desc = models.CharField(max_length = 128, blank = True) - file = models.FileField(upload_to = 'mp3s/%Y/%m/%d/') - slug = models.SlugField(unique = True) - - def __unicode__(self): - return self.title - - class Meta: - ordering = ('title', ) - verbose_name = "MP3" - -####################################################################### - -class Video_Set(models.Model): - date = models.DateField(blank=True) - title = models.CharField(max_length = 64) - text = models.TextField() - - def __unicode__(self): - return self.title - - class Meta: - ordering = ('date', ) - verbose_name = "Video Set" - - def save(self, *args, **kwargs): - if not self.id: - self.date = datetime.date.today() - - super(Video_Set, self).save(*args, **kwargs) - -####################################################################### - -class Video(models.Model): - video_set = models.ForeignKey(Video_Set) - title = models.CharField(max_length = 64) - embed_code = models.CharField(max_length = 1024) - - def __unicode__(self): - return self.title - - class Meta: - ordering = ('title', ) - -####################################################################### - -class Record_Label(models.Model): - name = models.CharField(max_length = 64) - url = models.URLField(verify_exists = False, max_length = 200) - - def __unicode__(self): - return self.name - - class Meta: - verbose_name = 'Record Label' - -####################################################################### - -class Album(models.Model): - title = models.CharField(max_length = 64) - photo = models.ForeignKey(Photo) - desc = models.TextField(blank = True) - - def __unicode__(self): - return self.title - - class Meta: - pass - -####################################################################### - -class Album_Track(models.Model): - album = models.ForeignKey(Album) - track_number = models.SmallIntegerField() - track_name = models.CharField(max_length = 64) - - def __unicode__(self): - return self.track_name - - class Meta: - verbose_name = 'Album Track' - ordering = ('album', 'track_number', ) - -####################################################################### - -class Label_Release(models.Model): - record_label = models.ForeignKey(Record_Label) - album = models.ForeignKey(Album) - catalog_number = models.CharField(max_length = 32) - release_date = models.DateField() - - def __unicode__(self): - return u'%s %s %s' % (self.record_label.name, self.album.title, self.catalog_number) - - class Meta: - verbose_name = 'Label Release' - -####################################################################### - -class Album_Merchant(models.Model): - album = models.ForeignKey(Album) - name = models.CharField(max_length = 64) - url = models.URLField(verify_exists = False, max_length = 200) - - def __unicode__(self): - return u'%s (%s)' % (self.name, self.album.title) - - class Meta: - verbose_name = 'Album Merchant' - ordering = ('name', ) - -####################################################################### - -class Merchandise(models.Model): - name = models.CharField(max_length = 64) - desc = models.TextField() - price = models.DecimalField(max_digits = 5, decimal_places = 2) - in_stock = models.BooleanField() - photo = models.ForeignKey(Photo) - - def __unicode__(self): - return self.name - - class Meta: - verbose_name_plural = "Merchandise" - -####################################################################### - -class Fan(models.Model): - statusCodes = (('P', 'Pending'), ('A', 'Active'), ('L', 'Leaving')) - keyLength = 16 - - name = models.CharField(max_length = 32, blank = True) - email = models.EmailField(db_index = True) - location = models.CharField(max_length = 64, blank = True) - status = models.CharField(max_length = 1, choices = statusCodes, default = 'A', - editable = False, db_index = True) - key = models.CharField(max_length = keyLength, editable = False, blank = True, db_index = True) - status_date = models.DateField(default = datetime.date.today, editable = False, db_index = True) - - def __unicode__(self): - if self.name: - return u'%s <%s>' % (self.name, self.email) - return self.email - - class Meta: - ordering = ('name', 'email') - - def setPending(self): - self.status = 'P' - self.status_date = datetime.date.today() - self.genKey() - - def setActive(self): - self.status = 'A' - self.status_date = datetime.date.today() - - def setLeaving(self): - self.status = 'L' - self.status_date = datetime.date.today() - self.genKey() - - def isPending(self): - return self.status == 'P' - - def isLeaving(self): - return self.status == 'L' - - def isActive(self): - return self.status == 'A' - - def current_status(self): - if self.status == 'P': - return 'Pending' - elif self.status == 'L': - return 'Leaving' - elif self.status == 'A': - return 'Active' - else: - return 'Unknown' - - def genKey(self): - self.key = ''.join(random.sample(string.ascii_letters + string.digits, self.keyLength)) -
--- a/mysite/band/urls.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -from django.conf.urls.defaults import * - -urlpatterns = patterns('band.views', - (r'^$', 'index'), - (r'^bio/$', 'bio'), - (r'^buy/$', 'buy'), - (r'^contact/$', 'contact'), - (r'^gigs/$', 'gigs'), - (r'^gigs/flyers$', 'flyers'), - (r'^mail/$', 'mail'), - (r'^mail/confirm/([a-zA-Z0-9]+)$', 'mail_confirm'), - (r'^mail/not_found$', 'mail_not_found'), - (r'^mail/thanks$', 'mail_thanks'), - (r'^mail/unsubscribe$', 'mail_unsubscribe'), - (r'^news/$', 'news'), - (r'^photos/$', 'photos_index'), - (r'^photos/(\d+)$', 'photo_detail'), - (r'^press/$', 'press_index'), - (r'^press/(\d+)$', 'press_detail'), - (r'^songs/$', 'songs'), - (r'^videos/$', 'videos_index'), - (r'^videos/(\d+)$', 'video_detail'), -) - -urlpatterns += patterns('band.admin_views', - (r'^admin/band/email/$', 'email'), - (r'^admin/band/email_sent/$', 'email_sent'), -)
--- a/mysite/band/views.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,340 +0,0 @@ -####################################################################### -# -# PyBand Copyright (C) 2008 - 2011 by Brian Neal -# -####################################################################### -import collections -import datetime -import random - -from django import forms -from django.core.urlresolvers import reverse -from django.http import HttpResponse -from django.http import HttpResponseRedirect -from django.shortcuts import render_to_response -from django.shortcuts import get_object_or_404 -from django.template import RequestContext -from django.template.loader import render_to_string -from django.core.mail import send_mail -from django.db import connection - -from band.models import Article -from band.models import Album -from band.models import Band -from band.models import Fan -from band.models import Gear -from band.models import Gig -from band.models import Member -from band.models import Merchandise -from band.models import Mp3 -from band.models import Mp3_Set -from band.models import News -from band.models import SiteConfig -from band.models import Video_Set -from photologue.models import Gallery -from photologue.models import Photo - -####################################################################### - -def index(request): - config = SiteConfig.objects.get(pk = 1) - carpe = Photo.objects.get(title_slug = 'carpe-noctem') - sandstorm = Photo.objects.get(title_slug = 'sandstorm-cover') - ruins = Photo.objects.get(title_slug = 'ruins-cover') - - upcomingDates = Gig.objects.filter(date__gte = datetime.date.today).order_by('date')[:5] - - return render_to_response('band/index.html', - { - 'config' : config, - 'carpe' : carpe, - 'sandstorm' : sandstorm, - 'ruins' : ruins, - 'upcomingDates' : upcomingDates, - # 'tourPhotos' : tourPhotos, - }, - context_instance = RequestContext(request)) - -####################################################################### - -def bio(request): - members = Member.objects.exclude(is_active__exact = 0) - - return render_to_response('band/bio.html', - { 'members' : members, }, - context_instance = RequestContext(request)) - -####################################################################### - -def gigs(request): - today = datetime.date.today() - gigs = Gig.objects.select_related('venue', 'flyer', 'venue__city', - 'venue__city__state', 'venue__city__country') - upcoming = [] - previous = [] - - # To avoid many, many database hits in the template, we get all the - # bands out at once. We also get the many-to-many intermediate table - # that Django generated for us so we can associate bands to gigs. - # Since we don't know about this table we drop into raw SQL to get - # the contents. - - bands = dict((band.id, band) for band in Band.objects.all()) - cursor = connection.cursor() - cursor.execute('SELECT * FROM band_gig_bands') - gig_bands = collections.defaultdict(list) - for row in cursor.fetchall(): - gig_bands[row[1]].append(bands[row[2]]) - - for gig in gigs: - gig.bands_ = gig_bands[gig.id] - if gig.date >= today: - upcoming.append(gig) - else: - previous.append(gig) - - upcoming.reverse() - - stats = {} - venues = set() - cities = set() - states = set() - countries = set() - for gig in previous: - venues.add(gig.venue.id) - cities.add(gig.venue.city.id) - if gig.venue.city.state: - states.add(gig.venue.city.state.id) - if gig.venue.city.country: - countries.add(gig.venue.city.country.id) - - stats['count'] = len(previous) - stats['venues'] = len(venues) - stats['cities'] = len(cities) - stats['states'] = len(states) - stats['countries'] = len(countries) - stats['bands'] = len(bands) - - flyerGigs = Gig.objects.exclude(flyer__isnull = True).select_related( - 'venue', 'flyer').order_by('-date') - - return render_to_response('band/gigs.html', { - 'upcoming' : upcoming, - 'previous' : previous, - 'stats' : stats, - 'flyerGigs' : flyerGigs, - }, - context_instance = RequestContext(request)) - -####################################################################### - -def news(request): - news = News.objects.order_by('-date') - - return render_to_response('band/news.html', - { - 'news' : news - }, - context_instance = RequestContext(request)) - -####################################################################### - -def press_index(request): - articles = Article.objects.order_by('-date') - - return render_to_response('band/press.html', - { - 'articles' : articles - }, - context_instance = RequestContext(request)) - -####################################################################### - -def press_detail(request, id): - article = get_object_or_404(Article, pk = id) - - return render_to_response('band/press_detail.html', - { 'article' : article }, - context_instance = RequestContext(request)) - -####################################################################### - -def songs(request): - mp3Sets = Mp3_Set.objects.order_by('-date', '-id') - - return render_to_response('band/songs.html', - { 'mp3Sets' : mp3Sets }, - context_instance = RequestContext(request)) - -####################################################################### - -def photos_index(request): - galleries = Gallery.objects.values('title', 'id').order_by('-id') - - photos = Photo.objects.filter(is_public__exact = 1) - randomPhotos = random.sample(photos, 4) - - return render_to_response('band/photos.html', - { 'galleries' : galleries, 'randomPhotos' : randomPhotos }, - context_instance = RequestContext(request)) - -####################################################################### - -def photo_detail(request, id): - gallery = get_object_or_404(Gallery, pk = id) - photos = gallery.photos.order_by('id') - return render_to_response('band/photo_detail.html', - {'gallery' : gallery, 'photos': photos }, - context_instance = RequestContext(request)) - -####################################################################### - -def videos_index(request): - vidsets = Video_Set.objects.values('title', 'id').order_by('-date') - return render_to_response('band/videos.html', - { 'vidsets' : vidsets }, - context_instance = RequestContext(request)) - -####################################################################### - -def video_detail(request, id): - vidset = get_object_or_404(Video_Set, pk = id) - - return render_to_response('band/video_detail.html', - { 'vidset' : vidset }, - context_instance = RequestContext(request)) - -####################################################################### - -def buy(request): - albums = Album.objects.all().order_by('-id') - merchandise = Merchandise.objects.all().order_by('-id') - config = SiteConfig.objects.values('ordering_info').get(pk = 1) - return render_to_response('band/buy.html', - { 'albums' : albums, 'merchandise' : merchandise, 'config' : config }, - context_instance = RequestContext(request)) - -####################################################################### - -def confirmEmail(config, to, subscribe, key): - band = config.band_name - fromEmail = config.contact_email - url = config.url - if url[-1] != '/': - url += '/' - url += 'mail/confirm/' + key - - if subscribe: - emailTemplate = 'band/email_subscribe.txt' - else: - emailTemplate = 'band/email_unsubscribe.txt' - - msg = render_to_string(emailTemplate, { 'band' : band, 'url' : url, 'band_url' : config.url }) - - subject = '[' + band + '] Mailing List Confirmation' - - send_mail(subject, msg, fromEmail, [to]) - -####################################################################### - -def contact(request): - config = SiteConfig.objects.get(pk = 1) - band = Member.objects.exclude(is_active__exact = 0).order_by('order') - return render_to_response('band/contact.html', - { 'config' : config, 'band' : band }, - context_instance = RequestContext(request)) - -####################################################################### - -class ContactForm(forms.Form): - name = forms.CharField(max_length = 32, required = False, - widget = forms.TextInput(attrs = {'class' : 'form-box'})) - email = forms.EmailField(widget = forms.TextInput(attrs = {'class' : 'form-box'})) - location = forms.CharField(max_length = 32, required = False, - widget = forms.TextInput(attrs = {'class' : 'form-box'})) - option = forms.ChoiceField(choices = (('subscribe', 'Subscribe'), ('unsubscribe', 'Unsubscribe')), - widget = forms.Select(attrs = {'class' : 'form-box'})) - -def mail(request): - config = SiteConfig.objects.get(pk = 1) - form = ContactForm() - if request.method == 'POST': - form = ContactForm(request.POST) - if form.is_valid(): - if form.cleaned_data['option'] == 'unsubscribe': - try: - fan = Fan.objects.get(email = form.cleaned_data['email']) - except Fan.DoesNotExist: - return HttpResponseRedirect(reverse(mail_not_found)) - - fan.setLeaving() - fan.save() - confirmEmail(config, fan.email, False, fan.key) - return HttpResponseRedirect(reverse(mail_unsubscribe)) - - elif form.cleaned_data['option'] == 'subscribe': - try: - fan = Fan.objects.get(email = form.cleaned_data['email']) - except Fan.DoesNotExist: - fan = Fan(name = form.cleaned_data['name'], - email = form.cleaned_data['email'], - location = form.cleaned_data['location']) - - fan.setPending() - fan.save() - confirmEmail(config, fan.email, True, fan.key) - return HttpResponseRedirect(reverse(mail_thanks)) - - return render_to_response('band/mail.html', - { 'form' : form }, - context_instance = RequestContext(request)) - -####################################################################### - -def mail_not_found(request): - return render_to_response('band/mail_not_found.html', - {}, - context_instance = RequestContext(request)) - -####################################################################### - -def mail_thanks(request): - return render_to_response('band/mail_thanks.html', - {}, - context_instance = RequestContext(request)) - -####################################################################### - -def mail_unsubscribe(request): - return render_to_response('band/mail_unsubscribe.html', - {}, - context_instance = RequestContext(request)) - -####################################################################### - -def mail_confirm(request, key): - fan = get_object_or_404(Fan, key = key) - - email = fan.email - action = 'subscribed' - - if fan.isPending(): - fan.setActive() - fan.save() - elif fan.isLeaving(): - fan.delete() - action = 'unsubscribed' - - return render_to_response('band/mail_confirm.html', - { 'email' : email, 'action' : action }, - context_instance = RequestContext(request)) - -####################################################################### - -def flyers(request): - - gigs = Gig.objects.exclude(flyer__isnull = True).order_by('-date') - - return render_to_response('band/flyers.html', - { 'gigs' : gigs }, - context_instance = RequestContext(request))
--- a/mysite/manage.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -#!/usr/bin/env python -from django.core.management import execute_manager -try: - import settings # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) - sys.exit(1) - -if __name__ == "__main__": - execute_manager(settings)
--- a/mysite/photologue/LICENSE.txt Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -Copyright (c) 2007-2008, Justin C. Driscoll -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of django-photologue nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- a/mysite/photologue/README.txt Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -Installation - -Step 1 - Download Photologue - -Photologue can be downloaded below or from the project page. Older versions are also available from the project page and users who like to live on the edge can checkout a copy of the latest trunk revision. - -Step 2 - Add Photologue To Your Project - -Copy the entire Photologue application folder (the folder named 'photologue' that contains 'models.py') to a location on your Python path such as your project root. Your project root is typically the directory where your 'settings.py' is found. - -Step 3 - Configure Your Settings - -Add 'photologue' to your INSTALLED_APPS setting: - - INSTALLED_APPS = ( - # ...other installed applications, - 'photologue', - ) - -Confirm that your MEDIA_ROOT and MEDIA_URL settings are correct. - -If you want to tweak things even more you can also over-ride a few default settings (optional, see documentation for more information on the available settings). - -Step 4 - Register Photologue with the Django Admin - -Add the following to your projects urls.py file: - - from django.contrib import admin - - admin.autodiscover() - -Step 4 - Sync Your Database - -Run the 'manage.py syndb' command to create the appropriate tables. After the database in initialized, Photologue will walk you through creating some default models. - -Additional documentation available here: - -http://code.google.com/p/django-photologue/w/list
--- a/mysite/photologue/admin.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -""" Newforms Admin configuration for Photologue - -""" -from django.contrib import admin -from models import * - -class GalleryAdmin(admin.ModelAdmin): - list_display = ('title', 'date_added', 'photo_count', 'is_public') - list_filter = ['date_added', 'is_public'] - date_hierarchy = 'date_added' - prepopulated_fields = {'title_slug': ('title',)} - filter_horizontal = ('photos',) - -class PhotoAdmin(admin.ModelAdmin): - list_display = ('title', 'date_taken', 'date_added', 'is_public', 'tags', 'view_count', 'admin_thumbnail') - list_filter = ['date_added', 'is_public'] - list_per_page = 10 - prepopulated_fields = {'title_slug': ('title',)} - -class PhotoEffectAdmin(admin.ModelAdmin): - list_display = ('name', 'description', 'admin_sample') - fieldsets = ( - (None, { - 'fields': ('name', 'description') - }), - ('Adjustments', { - 'fields': ('color', 'brightness', 'contrast', 'sharpness') - }), - ('Filters', { - 'fields': ('filters',) - }), - ('Reflection', { - 'fields': ('reflection_size', 'reflection_strength', 'background_color') - }), - ) - -class PhotoSizeAdmin(admin.ModelAdmin): - list_display = ('name', 'width', 'height', 'crop', 'pre_cache', 'effect', 'increment_count') - fieldsets = ( - (None, { - 'fields': ('name', 'width', 'height', 'quality') - }), - ('Options', { - 'fields': ('upscale', 'crop', 'pre_cache', 'increment_count') - }), - ('Enhancements', { - 'fields': ('effect', 'watermark',) - }), - ) - -class WatermarkAdmin(admin.ModelAdmin): - list_display = ('name', 'opacity', 'style') - - -admin.site.register(Gallery, GalleryAdmin) -admin.site.register(GalleryUpload) -admin.site.register(Photo, PhotoAdmin) -admin.site.register(PhotoEffect, PhotoEffectAdmin) -admin.site.register(PhotoSize, PhotoSizeAdmin) -admin.site.register(Watermark, WatermarkAdmin) \ No newline at end of file
--- a/mysite/photologue/locale/pl/LC_MESSAGES/django.po Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,419 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: Photologue Preview 2\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-07-22 23:05+0200\n" -"PO-Revision-Date: 2008-07-22 23:08+0100\n" -"Last-Translator: Jakub Wiśniowski <restless.being@gmail.com>\n" -"Language-Team: Jakub Wiśniowski <restless.being@gmail.com>\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n\n" -"X-Poedit-Language: Polish\n" -"X-Poedit-Country: POLAND\n" -"X-Poedit-SourceCharset: utf-8\n" - -#: models.py:32 -msgid "Photologue was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path." -msgstr "Photologue nie był w stanie zaimportować Python Imaging Library. Upewnij się, że pakiet ten jest zainstalowany i znajduje się w ścieżce dostępnej dla Pythona." - -#: models.py:38 -msgid "Separate tags with spaces, put quotes around multiple-word tags." -msgstr "Rozdziel tagi spacjami, ujmij w cudzysłowy tagi złożone z wielu słów." - -#: models.py:47 -msgid "Django-tagging was not found, tags will be treated as plain text." -msgstr "Django-tagging nie zostało znalezione. Tagi będą traktowane jako czysty tekst." - -#: models.py:64 -msgid "Very Low" -msgstr "Bardzo niska" - -#: models.py:65 -msgid "Low" -msgstr "Niska" - -#: models.py:66 -msgid "Medium-Low" -msgstr "Niższa średnia" - -#: models.py:67 -msgid "Medium" -msgstr "Średnia" - -#: models.py:68 -msgid "Medium-High" -msgstr "Wyższa średnia" - -#: models.py:69 -msgid "High" -msgstr "Wysoka" - -#: models.py:70 -msgid "Very High" -msgstr "Bardzo wysoka" - -#: models.py:75 -msgid "Top" -msgstr "Góra" - -#: models.py:76 -msgid "Right" -msgstr "Prawo" - -#: models.py:77 -msgid "Bottom" -msgstr "Dół" - -#: models.py:78 -msgid "Left" -msgstr "Lewo" - -#: models.py:79 -msgid "Center (Default)" -msgstr "Środek (Domyślnie)" - -#: models.py:83 -msgid "Flip left to right" -msgstr "Odbij w poziomie" - -#: models.py:84 -msgid "Flip top to bottom" -msgstr "Odbij w pionie" - -#: models.py:85 -msgid "Rotate 90 degrees counter-clockwise" -msgstr "Odwróć 90 stopni w lewo" - -#: models.py:86 -msgid "Rotate 90 degrees clockwise" -msgstr "Odwróć 90 stopni w prawo" - -#: models.py:87 -msgid "Rotate 180 degrees" -msgstr "Obróć o 180 stopni" - -#: models.py:91 -msgid "Tile" -msgstr "Kafelki" - -#: models.py:92 -msgid "Scale" -msgstr "Skaluj" - -#: models.py:102 -#, python-format -msgid "Chain multiple filters using the following pattern \"FILTER_ONE->FILTER_TWO->FILTER_THREE\". Image filters will be applied in order. The following filter are available: %s." -msgstr "Połącz wiele filtrów używając następującego wzorca: \"FILTR_PIERWSZY->FILTR_DRUGI->FILTR_TRZECI\". Filtry obrazów będą zastosowane w kolejności. Dostępne są następujące filtry: %s." - -#: models.py:107 -msgid "date published" -msgstr "data publikacji" - -#: models.py:108 -#: models.py:164 -#: models.py:448 -msgid "title" -msgstr "tytuł" - -#: models.py:109 -msgid "title slug" -msgstr "tytuł - slug " - -#: models.py:110 -msgid "A \"slug\" is a unique URL-friendly title for an object." -msgstr "\"Slug\" jest unikalnym, zgodnym z formatem dla URL-i tytułem obiektu." - -#: models.py:111 -#: models.py:166 -#: models.py:483 -msgid "description" -msgstr "opis" - -#: models.py:112 -#: models.py:167 -#: models.py:453 -msgid "is public" -msgstr "jest publiczna" - -#: models.py:113 -msgid "Public galleries will be displayed in the default views." -msgstr "Galerie publiczne będą wyświetlana w domyślnych widokach." - -#: models.py:114 -#: models.py:460 -msgid "photos" -msgstr "zdjęcia" - -#: models.py:116 -#: models.py:168 -#: models.py:454 -msgid "tags" -msgstr "tagi" - -#: models.py:121 -msgid "gallery" -msgstr "galeria" - -#: models.py:122 -msgid "galleries" -msgstr "galerie" - -#: models.py:155 -msgid "count" -msgstr "ilość" - -#: models.py:162 -msgid "images file (.zip)" -msgstr "plik z obrazami (.zip)" - -#: models.py:163 -msgid "Select a .zip file of images to upload into a new Gallery." -msgstr "Wybierz plik .zip zawierający zdjęcia które chcesz załadować do nowej Galerii." - -#: models.py:164 -msgid "All photos in the gallery will be given a title made up of the gallery title + a sequential number." -msgstr "Wszystkie " - -#: models.py:165 -#: models.py:451 -msgid "caption" -msgstr "podpis" - -#: models.py:165 -msgid "Caption will be added to all photos." -msgstr "Podpis będzie dodany do wszystkich zdjęć." - -#: models.py:166 -msgid "A description of this Gallery." -msgstr "Opis tej Galerii." - -#: models.py:167 -msgid "Uncheck this to make the uploaded gallery and included photographs private." -msgstr "Odznacz aby uczynić wrzucaną galerię oraz zawarte w niej zdjęcia prywatnymi." - -#: models.py:171 -msgid "gallery upload" -msgstr "wrzucona galeria" - -#: models.py:172 -msgid "gallery uploads" -msgstr "wrzucone galerie" - -#: models.py:228 -#: models.py:594 -msgid "image" -msgstr "obraz" - -#: models.py:229 -msgid "date taken" -msgstr "data wykonania" - -#: models.py:231 -msgid "crop from" -msgstr "obetnij z" - -#: models.py:232 -msgid "effect" -msgstr "efekt" - -#: models.py:250 -msgid "An \"admin_thumbnail\" photo size has not been defined." -msgstr "Rozmiar zdjęcia \"admin_thumbnail\" nie został zdefiniowany." - -#: models.py:258 -msgid "Thumbnail" -msgstr "Miniaturka" - -#: models.py:449 -msgid "slug" -msgstr "slug" - -#: models.py:452 -msgid "date added" -msgstr "data dodania" - -#: models.py:453 -msgid "Public photographs will be displayed in the default views." -msgstr "Publiczne zdjęcia będą wyświetlane w domyślnych widokach." - -#: models.py:459 -msgid "photo" -msgstr "zdjęcie" - -#: models.py:482 -#: models.py:608 -msgid "name" -msgstr "nazwa" - -#: models.py:554 -msgid "rotate or flip" -msgstr "obróć lub odbij" - -#: models.py:555 -#: models.py:562 -msgid "color" -msgstr "kolor" - -#: models.py:555 -msgid "A factor of 0.0 gives a black and white image, a factor of 1.0 gives the original image." -msgstr "Współczynnik 0.0 daje czarno-biały obraz, współczynnik 1.0 daje obraz oryginalny." - -#: models.py:556 -msgid "brightness" -msgstr "jasność" - -#: models.py:556 -msgid "A factor of 0.0 gives a black image, a factor of 1.0 gives the original image." -msgstr "Współczynnik 0.0 daje czarny obraz, współczynnik 1.0 daje obraz oryginalny." - -#: models.py:557 -msgid "contrast" -msgstr "kontrast" - -#: models.py:557 -msgid "A factor of 0.0 gives a solid grey image, a factor of 1.0 gives the original image." -msgstr "Współczynnik 0.0 daje jednolity szary obraz, współczynnik 1.0 daje obraz oryginalny." - -#: models.py:558 -msgid "sharpness" -msgstr "ostrość" - -#: models.py:558 -msgid "A factor of 0.0 gives a blurred image, a factor of 1.0 gives the original image." -msgstr "Współczynnik 0.0 daje rozmazany obraz, współczynnik 1.0 daje obraz oryginalny." - -#: models.py:559 -msgid "filters" -msgstr "filtry" - -#: models.py:560 -msgid "size" -msgstr "rozmiar" - -#: models.py:560 -msgid "The height of the reflection as a percentage of the orignal image. A factor of 0.0 adds no reflection, a factor of 1.0 adds a reflection equal to the height of the orignal image." -msgstr "Wysokość odbicia jako procent oryginalnego obrazu. Współczynnik 0.0 nie dodaje odbicia, współczynnik 1.0 dodaje odbicie równe wysokości oryginalnego obrazu." - -#: models.py:561 -msgid "strength" -msgstr "intensywność" - -#: models.py:565 -#: models.py:616 -msgid "photo effect" -msgstr "efekt zdjęcia" - -#: models.py:566 -msgid "photo effects" -msgstr "efekty zdjęć" - -#: models.py:595 -msgid "style" -msgstr "styl" - -#: models.py:596 -msgid "opacity" -msgstr "przeźroczystość" - -#: models.py:596 -msgid "The opacity of the overlay." -msgstr "Poziom przezroczystości" - -#: models.py:599 -msgid "watermark" -msgstr "znak wodny" - -#: models.py:600 -msgid "watermarks" -msgstr "znaki wodne" - -#: models.py:608 -msgid "Photo size name should contain only letters, numbers and underscores. Examples: \"thumbnail\", \"display\", \"small\", \"main_page_widget\"." -msgstr "Nazwa rozmiaru zdjęcia powinna zawierać tylko litery, cyfry i podkreślenia. Przykłady: \"miniatura\", \"wystawa\", \"male\", \"widget_strony_glownej\"." - -#: models.py:609 -msgid "width" -msgstr "szerokość" - -#: models.py:609 -msgid "If width is set to \"0\" the image will be scaled to the supplied height." -msgstr "Jeśli szerokość jest ustawiona na \"0\" to obraz będzie skalowany do podanej wysokości." - -#: models.py:610 -msgid "height" -msgstr "wysokość" - -#: models.py:610 -msgid "If height is set to \"0\" the image will be scaled to the supplied width" -msgstr "Jeśli wysokość jest ustawiona na \"0\" to obraz będzie skalowany do podanej szerokości." - -#: models.py:611 -msgid "quality" -msgstr "jakość" - -#: models.py:611 -msgid "JPEG image quality." -msgstr "Jakość obrazu JPEG" - -#: models.py:612 -msgid "upscale images?" -msgstr "skalować obrazy w górę?" - -#: models.py:612 -msgid "If selected the image will be scaled up if necessary to fit the supplied dimensions. Cropped sizes will be upscaled regardless of this setting." -msgstr "Jeśli zaznaczone to obraz będzie skalowany w górę tak aby pasował do podanych wymiarów. Obcinane rozmiary będą skalowane niezależnie od tego ustawienia." - -#: models.py:613 -msgid "crop to fit?" -msgstr "przyciąć aby pasował?" - -#: models.py:613 -msgid "If selected the image will be scaled and cropped to fit the supplied dimensions." -msgstr "Jeśli zaznaczone to obraz będzie skalowany i przycinany tak aby pasował do podanych wymiarów." - -#: models.py:614 -msgid "pre-cache?" -msgstr "wstępnie cachować?" - -#: models.py:614 -msgid "If selected this photo size will be pre-cached as photos are added." -msgstr "Jesli zaznaczone to ten rozmiar zdjęć będzie wstępnie cachowany przy dodawaniu zdjęć." - -#: models.py:615 -msgid "increment view count?" -msgstr "zwiększyć licznik odsłon?" - -#: models.py:615 -msgid "If selected the image's \"view_count\" will be incremented when this photo size is displayed." -msgstr "Jeśli zaznaczone to \"licznik_odslon\" będzie zwiększany gdy ten rozmiar zdjęcia będzie wyświetlany." - -#: models.py:617 -msgid "watermark image" -msgstr "oznacz kluczem wodnym" - -#: models.py:621 -msgid "photo size" -msgstr "rozmiar zdjęcia" - -#: models.py:622 -msgid "photo sizes" -msgstr "rozmiary zdjęć" - -#: models.py:640 -msgid "A PhotoSize must have a positive height or width." -msgstr "PhotoSize musi mieć dodatnią wysokość i szerokość." - -#~ msgid "Leave to size the image to the set height" -#~ msgstr "Ustaw aby przeskalować obraz do wybranej wysokości" -#~ msgid "Leave to size the image to the set width" -#~ msgstr "Ustaw aby przeskalować obraz do wybranej szerokości" -#~ msgid "original image" -#~ msgstr "oryginalny obraz" -
--- a/mysite/photologue/management/__init__.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -
--- a/mysite/photologue/management/commands/__init__.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -from photologue.models import PhotoSize - -def get_response(msg, func=int, default=None): - while True: - resp = raw_input(msg) - if not resp and default is not None: - return default - try: - return func(resp) - except: - print 'Invalid input.' - -def create_photosize(name, width=0, height=0, crop=False, pre_cache=False, increment_count=False): - try: - size = PhotoSize.objects.get(name=name) - exists = True - except PhotoSize.DoesNotExist: - size = PhotoSize(name=name) - exists = False - if exists: - msg = 'A "%s" photo size already exists. Do you want to replace it? (yes, no):' % name - if not get_response(msg, lambda inp: inp == 'yes', False): - return - print '\nWe will now define the "%s" photo size:\n' % size - w = get_response('Width (in pixels):', lambda inp: int(inp), width) - h = get_response('Height (in pixels):', lambda inp: int(inp), height) - c = get_response('Crop to fit? (yes, no):', lambda inp: inp == 'yes', crop) - p = get_response('Pre-cache? (yes, no):', lambda inp: inp == 'yes', pre_cache) - i = get_response('Increment count? (yes, no):', lambda inp: inp == 'yes', increment_count) - size.width = w - size.height = h - size.crop = c - size.pre_cache = p - size.increment_count = i - size.save() - print '\nA "%s" photo size has been created.\n' % name - return size \ No newline at end of file
--- a/mysite/photologue/management/commands/plcache.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -from django.core.management.base import BaseCommand, CommandError -from optparse import make_option -from photologue.models import PhotoSize, ImageModel - -class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('--reset', '-r', action='store_true', dest='reset', help='Reset photo cache before generating'), - ) - - help = ('Manages Photologue cache file for the given sizes.') - args = '[sizes]' - - requires_model_validation = True - can_import_settings = True - - def handle(self, *args, **options): - return create_cache(args, options) - -def create_cache(sizes, options): - """ - Creates the cache for the given files - """ - reset = options.get('reset', None) - - size_list = [size.strip(' ,') for size in sizes] - - if len(size_list) < 1: - sizes = PhotoSize.objects.filter(pre_cache=True) - else: - sizes = PhotoSize.objects.filter(name__in=size_list) - - if not len(sizes): - raise CommandError('No photo sizes were found.') - - print 'Caching photos, this may take a while...' - - for cls in ImageModel.__subclasses__(): - for photosize in sizes: - print 'Cacheing %s size images' % photosize.name - for obj in cls.objects.all(): - if reset: - obj.remove_size(photosize) - obj.create_size(photosize)
--- a/mysite/photologue/management/commands/plcreatesize.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -from django.core.management.base import BaseCommand, CommandError -from photologue.management.commands import create_photosize - -class Command(BaseCommand): - help = ('Creates a new Photologue photo size interactively.') - requires_model_validation = True - can_import_settings = True - - def handle(self, *args, **options): - create_size(args[0]) - -def create_size(size): - create_photosize(size) \ No newline at end of file
--- a/mysite/photologue/management/commands/plflush.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -from django.core.management.base import BaseCommand, CommandError -from optparse import make_option -from photologue.models import PhotoSize, ImageModel - -class Command(BaseCommand): - help = ('Clears the Photologue cache for the given sizes.') - args = '[sizes]' - - requires_model_validation = True - can_import_settings = True - - def handle(self, *args, **options): - return create_cache(args, options) - -def create_cache(sizes, options): - """ - Clears the cache for the given files - """ - size_list = [size.strip(' ,') for size in sizes] - - if len(size_list) < 1: - sizes = PhotoSize.objects.all() - else: - sizes = PhotoSize.objects.filter(name__in=size_list) - - if not len(sizes): - raise CommandError('No photo sizes were found.') - - print 'Flushing cache...' - - for cls in ImageModel.__subclasses__(): - for photosize in sizes: - print 'Flushing %s size images' % photosize.name - for obj in cls.objects.all(): - obj.remove_size(photosize)
--- a/mysite/photologue/management/commands/plinit.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -from django.core.management.base import BaseCommand, CommandError -from photologue.management.commands import get_response, create_photosize -from photologue.models import PhotoEffect - -class Command(BaseCommand): - help = ('Prompts the user to set up the default photo sizes required by Photologue.') - requires_model_validation = True - can_import_settings = True - - def handle(self, *args, **kwargs): - return init(*args, **kwargs) - -def init(*args, **kwargs): - msg = '\nPhotologue requires a specific photo size to display thumbnail previews in the Django admin application.\nWould you like to generate this size now? (yes, no):' - if get_response(msg, lambda inp: inp == 'yes', False): - admin_thumbnail = create_photosize('admin_thumbnail', width=100, height=75, crop=True, pre_cache=True) - msg = 'Would you like to apply a sample enhancement effect to your admin thumbnails? (yes, no):' - if get_response(msg, lambda inp: inp == 'yes', False): - effect, created = PhotoEffect.objects.get_or_create(name='Enhance Thumbnail', description="Increases sharpness and contrast. Works well for smaller image sizes such as thumbnails.", contrast=1.2, sharpness=1.3) - admin_thumbnail.effect = effect - admin_thumbnail.save() - msg = '\nPhotologue comes with a set of templates for setting up a complete photo gallery. These templates require you to define both a "thumbnail" and "display" size.\nWould you like to define them now? (yes, no):' - if get_response(msg, lambda inp: inp == 'yes', False): - thumbnail = create_photosize('thumbnail', width=100, height=75) - display = create_photosize('display', width=400, increment_count=True) - msg = 'Would you like to apply a sample reflection effect to your display images? (yes, no):' - if get_response(msg, lambda inp: inp == 'yes', False): - effect, created = PhotoEffect.objects.get_or_create(name='Display Reflection', description="Generates a reflection with a white background", reflection_size=0.4) - display.effect = effect - display.save() \ No newline at end of file
--- a/mysite/photologue/models.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,700 +0,0 @@ -import os -import random -import shutil -import zipfile - -from datetime import datetime -from inspect import isclass - -from django.db import models -from django.db.models.signals import post_init -from django.conf import settings -from django.core.files.base import ContentFile -from django.core.urlresolvers import reverse -from django.template.defaultfilters import slugify -from django.utils.functional import curry -from django.utils.translation import ugettext_lazy as _ - -# Required PIL classes may or may not be available from the root namespace -# depending on the installation method used. -try: - import Image - import ImageFile - import ImageFilter - import ImageEnhance -except ImportError: - try: - from PIL import Image - from PIL import ImageFile - from PIL import ImageFilter - from PIL import ImageEnhance - except ImportError: - raise ImportError(_('Photologue was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.')) - -# attempt to load the django-tagging TagField from default location, -# otherwise we substitude a dummy TagField. -try: - from tagging.fields import TagField - tagfield_help_text = _('Separate tags with spaces, put quotes around multiple-word tags.') -except ImportError: - class TagField(models.CharField): - def __init__(self, **kwargs): - default_kwargs = {'max_length': 255, 'blank': True} - default_kwargs.update(kwargs) - super(TagField, self).__init__(**default_kwargs) - def get_internal_type(self): - return 'CharField' - tagfield_help_text = _('Django-tagging was not found, tags will be treated as plain text.') - -from utils import EXIF -from utils.reflection import add_reflection -from utils.watermark import apply_watermark - -# Path to sample image -SAMPLE_IMAGE_PATH = getattr(settings, 'SAMPLE_IMAGE_PATH', os.path.join(os.path.dirname(__file__), 'res', 'sample.jpg')) # os.path.join(settings.PROJECT_PATH, 'photologue', 'res', 'sample.jpg' - -# Modify image file buffer size. -ImageFile.MAXBLOCK = getattr(settings, 'PHOTOLOGUE_MAXBLOCK', 256 * 2 ** 10) - -# Photologue image path relative to media root -PHOTOLOGUE_DIR = getattr(settings, 'PHOTOLOGUE_DIR', 'photologue') - -# Look for user function to define file paths -PHOTOLOGUE_PATH = getattr(settings, 'PHOTOLOGUE_PATH', None) -if PHOTOLOGUE_PATH is not None: - if callable(PHOTOLOGUE_PATH): - get_storage_path = PHOTOLOGUE_PATH - else: - parts = PHOTOLOGUE_PATH.split('.') - module_name = '.'.join(parts[:-1]) - module = __import__(module_name) - get_storage_path = getattr(module, parts[-1]) -else: - def get_storage_path(instance, filename): - return os.path.join(PHOTOLOGUE_DIR, 'photos', filename) - -# Quality options for JPEG images -JPEG_QUALITY_CHOICES = ( - (30, _('Very Low')), - (40, _('Low')), - (50, _('Medium-Low')), - (60, _('Medium')), - (70, _('Medium-High')), - (80, _('High')), - (90, _('Very High')), -) - -# choices for new crop_anchor field in Photo -CROP_ANCHOR_CHOICES = ( - ('top', _('Top')), - ('right', _('Right')), - ('bottom', _('Bottom')), - ('left', _('Left')), - ('center', _('Center (Default)')), -) - -IMAGE_TRANSPOSE_CHOICES = ( - ('FLIP_LEFT_RIGHT', _('Flip left to right')), - ('FLIP_TOP_BOTTOM', _('Flip top to bottom')), - ('ROTATE_90', _('Rotate 90 degrees counter-clockwise')), - ('ROTATE_270', _('Rotate 90 degrees clockwise')), - ('ROTATE_180', _('Rotate 180 degrees')), -) - -WATERMARK_STYLE_CHOICES = ( - ('tile', _('Tile')), - ('scale', _('Scale')), -) - -# Prepare a list of image filters -filter_names = [] -for n in dir(ImageFilter): - klass = getattr(ImageFilter, n) - if isclass(klass) and issubclass(klass, ImageFilter.BuiltinFilter) and \ - hasattr(klass, 'name'): - filter_names.append(klass.__name__) -IMAGE_FILTERS_HELP_TEXT = _('Chain multiple filters using the following pattern "FILTER_ONE->FILTER_TWO->FILTER_THREE". Image filters will be applied in order. The following filters are available: %s.' % (', '.join(filter_names))) - - -class Gallery(models.Model): - date_added = models.DateTimeField(_('date published'), default=datetime.now) - title = models.CharField(_('title'), max_length=100, unique=True) - title_slug = models.SlugField(_('title slug'), unique=True, - help_text=_('A "slug" is a unique URL-friendly title for an object.')) - description = models.TextField(_('description'), blank=True) - is_public = models.BooleanField(_('is public'), default=True, - help_text=_('Public galleries will be displayed in the default views.')) - photos = models.ManyToManyField('Photo', related_name='galleries', verbose_name=_('photos'), - null=True, blank=True) - tags = TagField(help_text=tagfield_help_text, verbose_name=_('tags')) - - class Meta: - ordering = ['-date_added'] - get_latest_by = 'date_added' - verbose_name = _('gallery') - verbose_name_plural = _('galleries') - - def __unicode__(self): - return self.title - - def __str__(self): - return self.__unicode__() - - def get_absolute_url(self): - return reverse('pl-gallery', args=[self.title_slug]) - - def latest(self, limit=0, public=True): - if limit == 0: - limit = self.photo_count() - if public: - return self.public()[:limit] - else: - return self.photos.all()[:limit] - - def sample(self, count=0, public=True): - if count == 0 or count > self.photo_count(): - count = self.photo_count() - if public: - photo_set = self.public() - else: - photo_set = self.photos.all() - return random.sample(photo_set, count) - - def photo_count(self, public=True): - if public: - return self.public().count() - else: - return self.photos.all().count() - photo_count.short_description = _('count') - - def public(self): - return self.photos.filter(is_public=True) - - -class GalleryUpload(models.Model): - zip_file = models.FileField(_('images file (.zip)'), upload_to=PHOTOLOGUE_DIR+"/temp", - help_text=_('Select a .zip file of images to upload into a new Gallery.')) - gallery = models.ForeignKey(Gallery, null=True, blank=True, help_text=_('Select a gallery to add these images to. leave this empty to create a new gallery from the supplied title.')) - title = models.CharField(_('title'), max_length=75, help_text=_('All photos in the gallery will be given a title made up of the gallery title + a sequential number.')) - caption = models.TextField(_('caption'), blank=True, help_text=_('Caption will be added to all photos.')) - description = models.TextField(_('description'), blank=True, help_text=_('A description of this Gallery.')) - is_public = models.BooleanField(_('is public'), default=True, help_text=_('Uncheck this to make the uploaded gallery and included photographs private.')) - tags = models.CharField(max_length=255, blank=True, help_text=tagfield_help_text, verbose_name=_('tags')) - - class Meta: - verbose_name = _('gallery upload') - verbose_name_plural = _('gallery uploads') - - def save(self): - super(GalleryUpload, self).save() - self.process_zipfile() - super(GalleryUpload, self).delete() - - def process_zipfile(self): - if os.path.isfile(self.zip_file.path): - # TODO: implement try-except here - zip = zipfile.ZipFile(self.zip_file.path) - bad_file = zip.testzip() - if bad_file: - raise Exception('"%s" in the .zip archive is corrupt.' % bad_file) - count = 1 - if self.gallery: - gallery = self.gallery - else: - gallery = Gallery.objects.create(title=self.title, - title_slug=slugify(self.title), - description=self.description, - is_public=self.is_public, - tags=self.tags) - from cStringIO import StringIO - for filename in zip.namelist(): - if filename.startswith('__'): # do not process meta files - continue - data = zip.read(filename) - if len(data): - try: - # the following is taken from django.newforms.fields.ImageField: - # load() is the only method that can spot a truncated JPEG, - # but it cannot be called sanely after verify() - trial_image = Image.open(StringIO(data)) - trial_image.load() - # verify() is the only method that can spot a corrupt PNG, - # but it must be called immediately after the constructor - trial_image = Image.open(StringIO(data)) - trial_image.verify() - except Exception: - # if a "bad" file is found we just skip it. - continue - while 1: - title = ' '.join([self.title, str(count)]) - slug = slugify(title) - try: - p = Photo.objects.get(title_slug=slug) - except Photo.DoesNotExist: - photo = Photo(title=title, title_slug=slug, - caption=self.caption, - is_public=self.is_public, - tags=self.tags) - photo.image.save(filename, ContentFile(data)) - gallery.photos.add(photo) - count = count + 1 - break - count = count + 1 - zip.close() - - -class ImageModel(models.Model): - image = models.ImageField(_('image'), upload_to=get_storage_path) - date_taken = models.DateTimeField(_('date taken'), null=True, blank=True, editable=False) - view_count = models.PositiveIntegerField(default=0, editable=False) - crop_from = models.CharField(_('crop from'), blank=True, max_length=10, default='center', choices=CROP_ANCHOR_CHOICES) - effect = models.ForeignKey('PhotoEffect', null=True, blank=True, related_name="%(class)s_related", verbose_name=_('effect')) - - class Meta: - abstract = True - - @property - def EXIF(self): - try: - return EXIF.process_file(open(self.image.path, 'rb')) - except: - try: - return EXIF.process_file(open(self.image.path, 'rb'), details=False) - except: - return {} - - def admin_thumbnail(self): - func = getattr(self, 'get_admin_thumbnail_url', None) - if func is None: - return _('An "admin_thumbnail" photo size has not been defined.') - else: - if hasattr(self, 'get_absolute_url'): - return u'<a href="%s"><img src="%s"></a>' % \ - (self.get_absolute_url(), func()) - else: - return u'<a href="%s"><img src="%s"></a>' % \ - (self.image.url, func()) - admin_thumbnail.short_description = _('Thumbnail') - admin_thumbnail.allow_tags = True - - def cache_path(self): - return os.path.join(os.path.dirname(self.image.path), "cache") - - def cache_url(self): - return '/'.join([os.path.dirname(self.image.url), "cache"]) - - def image_filename(self): - return os.path.basename(self.image.path) - - def _get_filename_for_size(self, size): - size = getattr(size, 'name', size) - base, ext = os.path.splitext(self.image_filename()) - return ''.join([base, '_', size, ext]) - - def _get_SIZE_photosize(self, size): - return PhotoSizeCache().sizes.get(size) - - def _get_SIZE_size(self, size): - photosize = PhotoSizeCache().sizes.get(size) - if not self.size_exists(photosize): - self.create_size(photosize) - return Image.open(self._get_SIZE_filename(size)).size - - def _get_SIZE_url(self, size): - photosize = PhotoSizeCache().sizes.get(size) - if not self.size_exists(photosize): - self.create_size(photosize) - if photosize.increment_count: - self.view_count += 1 - self.save(update=True) - return '/'.join([self.cache_url(), self._get_filename_for_size(photosize.name)]) - - def _get_SIZE_filename(self, size): - photosize = PhotoSizeCache().sizes.get(size) - return os.path.join(self.cache_path(), - self._get_filename_for_size(photosize.name)) - - def add_accessor_methods(self, *args, **kwargs): - for size in PhotoSizeCache().sizes.keys(): - setattr(self, 'get_%s_size' % size, - curry(self._get_SIZE_size, size=size)) - setattr(self, 'get_%s_photosize' % size, - curry(self._get_SIZE_photosize, size=size)) - setattr(self, 'get_%s_url' % size, - curry(self._get_SIZE_url, size=size)) - setattr(self, 'get_%s_filename' % size, - curry(self._get_SIZE_filename, size=size)) - - def size_exists(self, photosize): - func = getattr(self, "get_%s_filename" % photosize.name, None) - if func is not None: - if os.path.isfile(func()): - return True - return False - - def resize_image(self, im, photosize): - cur_width, cur_height = im.size - new_width, new_height = photosize.size - if photosize.crop: - ratio = max(float(new_width)/cur_width,float(new_height)/cur_height) - x = (cur_width * ratio) - y = (cur_height * ratio) - xd = abs(new_width - x) - yd = abs(new_height - y) - x_diff = int(xd / 2) - y_diff = int(yd / 2) - if self.crop_from == 'top': - box = (int(x_diff), 0, int(x_diff+new_width), new_height) - elif self.crop_from == 'left': - box = (0, int(y_diff), new_width, int(y_diff+new_height)) - elif self.crop_from == 'bottom': - box = (int(x_diff), int(yd), int(x_diff+new_width), int(y)) # y - yd = new_height - elif self.crop_from == 'right': - box = (int(xd), int(y_diff), int(x), int(y_diff+new_height)) # x - xd = new_width - else: - box = (int(x_diff), int(y_diff), int(x_diff+new_width), int(y_diff+new_height)) - im = im.resize((int(x), int(y)), Image.ANTIALIAS).crop(box) - else: - if not new_width == 0 and not new_height == 0: - ratio = min(float(new_width)/cur_width, - float(new_height)/cur_height) - else: - if new_width == 0: - ratio = float(new_height)/cur_height - else: - ratio = float(new_width)/cur_width - new_dimensions = (int(round(cur_width*ratio)), - int(round(cur_height*ratio))) - if new_dimensions[0] > cur_width or \ - new_dimensions[1] > cur_height: - if not photosize.upscale: - return im - im = im.resize(new_dimensions, Image.ANTIALIAS) - return im - - def create_size(self, photosize): - if self.size_exists(photosize): - return - if not os.path.isdir(self.cache_path()): - os.makedirs(self.cache_path()) - try: - im = Image.open(self.image.path) - except IOError: - return - # Apply effect if found - if self.effect is not None: - im = self.effect.pre_process(im) - elif photosize.effect is not None: - im = photosize.effect.pre_process(im) - # Resize/crop image - if im.size != photosize.size: - im = self.resize_image(im, photosize) - # Apply watermark if found - if photosize.watermark is not None: - im = photosize.watermark.post_process(im) - # Apply effect if found - if self.effect is not None: - im = self.effect.post_process(im) - elif photosize.effect is not None: - im = photosize.effect.post_process(im) - # Save file - im_filename = getattr(self, "get_%s_filename" % photosize.name)() - try: - if im.format == 'JPEG': - im.save(im_filename, 'JPEG', quality=int(photosize.quality), optimize=True) - else: - im.save(im_filename) - except IOError, e: - if os.path.isfile(im_filename): - os.unlink(im_filename) - raise e - - def remove_size(self, photosize, remove_dirs=True): - if not self.size_exists(photosize): - return - filename = getattr(self, "get_%s_filename" % photosize.name)() - if os.path.isfile(filename): - os.remove(filename) - if remove_dirs: - self.remove_cache_dirs() - - def clear_cache(self): - cache = PhotoSizeCache() - for photosize in cache.sizes.values(): - self.remove_size(photosize, False) - self.remove_cache_dirs() - - def pre_cache(self): - cache = PhotoSizeCache() - for photosize in cache.sizes.values(): - if photosize.pre_cache: - self.create_size(photosize) - - def remove_cache_dirs(self): - try: - os.removedirs(self.cache_path()) - except: - pass - - def save(self, update=False): - if update: - models.Model.save(self) - return - if self.date_taken is None: - try: - exif_date = self.EXIF.get('EXIF DateTimeOriginal', None) - if exif_date is not None: - d, t = str.split(exif_date.values) - year, month, day = d.split(':') - hour, minute, second = t.split(':') - self.date_taken = datetime(int(year), int(month), int(day), - int(hour), int(minute), int(second)) - except: - pass - if self.date_taken is None: - self.date_taken = datetime.now() - if self._get_pk_val(): - self.clear_cache() - super(ImageModel, self).save() - self.pre_cache() - - def delete(self): - self.clear_cache() - super(ImageModel, self).delete() - - -class Photo(ImageModel): - title = models.CharField(_('title'), max_length=100, unique=True) - title_slug = models.SlugField(_('slug'), unique=True, - help_text=('A "slug" is a unique URL-friendly title for an object.')) - caption = models.TextField(_('caption'), blank=True) - date_added = models.DateTimeField(_('date added'), default=datetime.now, editable=False) - is_public = models.BooleanField(_('is public'), default=True, help_text=_('Public photographs will be displayed in the default views.')) - tags = TagField(help_text=tagfield_help_text, verbose_name=_('tags')) - - class Meta: - ordering = ['-date_added'] - get_latest_by = 'date_added' - verbose_name = _("photo") - verbose_name_plural = _("photos") - - def __unicode__(self): - return self.title - - def __str__(self): - return self.__unicode__() - - def save(self, update=False): - if self.title_slug is None: - self.title_slug = slugify(self.title) - super(Photo, self).save(update) - - def get_absolute_url(self): - return reverse('pl-photo', args=[self.title_slug]) - - def public_galleries(self): - """Return the public galleries to which this photo belongs.""" - return self.galleries.filter(is_public=True) - - -class BaseEffect(models.Model): - name = models.CharField(_('name'), max_length=30, unique=True) - description = models.TextField(_('description'), blank=True) - - class Meta: - abstract = True - - def sample_dir(self): - return os.path.join(settings.MEDIA_ROOT, PHOTOLOGUE_DIR, 'samples') - - def sample_url(self): - return settings.MEDIA_URL + '/'.join([PHOTOLOGUE_DIR, 'samples', '%s %s.jpg' % (self.name.lower(), 'sample')]) - - def sample_filename(self): - return os.path.join(self.sample_dir(), '%s %s.jpg' % (self.name.lower(), 'sample')) - - def create_sample(self): - if not os.path.isdir(self.sample_dir()): - os.makedirs(self.sample_dir()) - try: - im = Image.open(SAMPLE_IMAGE_PATH) - except IOError: - raise IOError('Photologue was unable to open the sample image: %s.' % SAMPLE_IMAGE_PATH) - im = self.process(im) - im.save(self.sample_filename(), 'JPEG', quality=90, optimize=True) - - def admin_sample(self): - return u'<img src="%s">' % self.sample_url() - admin_sample.short_description = 'Sample' - admin_sample.allow_tags = True - - def pre_process(self, im): - return im - - def post_process(self, im): - return im - - def process(self, im): - im = self.pre_process(im) - im = self.post_process(im) - return im - - def __unicode__(self): - return self.name - - def __str__(self): - return self.__unicode__() - - def save(self): - try: - os.remove(self.sample_filename()) - except: - pass - models.Model.save(self) - self.create_sample() - for size in self.photo_sizes.all(): - size.clear_cache() - # try to clear all related subclasses of ImageModel - for prop in [prop for prop in dir(self) if prop[-8:] == '_related']: - for obj in getattr(self, prop).all(): - obj.clear_cache() - obj.pre_cache() - - def delete(self): - try: - os.remove(self.sample_filename()) - except: - pass - super(PhotoEffect, self).delete() - - -class PhotoEffect(BaseEffect): - """ A pre-defined effect to apply to photos """ - transpose_method = models.CharField(_('rotate or flip'), max_length=15, blank=True, choices=IMAGE_TRANSPOSE_CHOICES) - color = models.FloatField(_('color'), default=1.0, help_text=_("A factor of 0.0 gives a black and white image, a factor of 1.0 gives the original image.")) - brightness = models.FloatField(_('brightness'), default=1.0, help_text=_("A factor of 0.0 gives a black image, a factor of 1.0 gives the original image.")) - contrast = models.FloatField(_('contrast'), default=1.0, help_text=_("A factor of 0.0 gives a solid grey image, a factor of 1.0 gives the original image.")) - sharpness = models.FloatField(_('sharpness'), default=1.0, help_text=_("A factor of 0.0 gives a blurred image, a factor of 1.0 gives the original image.")) - filters = models.CharField(_('filters'), max_length=200, blank=True, help_text=_(IMAGE_FILTERS_HELP_TEXT)) - reflection_size = models.FloatField(_('size'), default=0, help_text=_("The height of the reflection as a percentage of the orignal image. A factor of 0.0 adds no reflection, a factor of 1.0 adds a reflection equal to the height of the orignal image.")) - reflection_strength = models.FloatField(_('strength'), default=0.6, help_text="The initial opacity of the reflection gradient.") - background_color = models.CharField(_('color'), max_length=7, default="#FFFFFF", help_text="The background color of the reflection gradient. Set this to match the background color of your page.") - - class Meta: - verbose_name = _("photo effect") - verbose_name_plural = _("photo effects") - - def pre_process(self, im): - if self.transpose_method != '': - method = getattr(Image, self.transpose_method) - im = im.transpose(method) - if im.mode != 'RGB' and im.mode != 'RGBA': - return im - for name in ['Color', 'Brightness', 'Contrast', 'Sharpness']: - factor = getattr(self, name.lower()) - if factor != 1.0: - im = getattr(ImageEnhance, name)(im).enhance(factor) - for name in self.filters.split('->'): - image_filter = getattr(ImageFilter, name.upper(), None) - if image_filter is not None: - try: - im = im.filter(image_filter) - except ValueError: - pass - return im - - def post_process(self, im): - if self.reflection_size != 0.0: - im = add_reflection(im, bgcolor=self.background_color, amount=self.reflection_size, opacity=self.reflection_strength) - return im - - -class Watermark(BaseEffect): - image = models.ImageField(_('image'), upload_to=PHOTOLOGUE_DIR+"/watermarks") - style = models.CharField(_('style'), max_length=5, choices=WATERMARK_STYLE_CHOICES, default='scale') - opacity = models.FloatField(_('opacity'), default=1, help_text=_("The opacity of the overlay.")) - - class Meta: - verbose_name = _('watermark') - verbose_name_plural = _('watermarks') - - def post_process(self, im): - mark = Image.open(self.image.path) - return apply_watermark(im, mark, self.style, self.opacity) - - -class PhotoSize(models.Model): - name = models.CharField(_('name'), max_length=20, unique=True, help_text=_('Photo size name should contain only letters, numbers and underscores. Examples: "thumbnail", "display", "small", "main_page_widget".')) - width = models.PositiveIntegerField(_('width'), default=0, help_text=_('If width is set to "0" the image will be scaled to the supplied height.')) - height = models.PositiveIntegerField(_('height'), default=0, help_text=_('If height is set to "0" the image will be scaled to the supplied width')) - quality = models.PositiveIntegerField(_('quality'), choices=JPEG_QUALITY_CHOICES, default=70, help_text=_('JPEG image quality.')) - upscale = models.BooleanField(_('upscale images?'), default=False, help_text=_('If selected the image will be scaled up if necessary to fit the supplied dimensions. Cropped sizes will be upscaled regardless of this setting.')) - crop = models.BooleanField(_('crop to fit?'), default=False, help_text=_('If selected the image will be scaled and cropped to fit the supplied dimensions.')) - pre_cache = models.BooleanField(_('pre-cache?'), default=False, help_text=_('If selected this photo size will be pre-cached as photos are added.')) - increment_count = models.BooleanField(_('increment view count?'), default=False, help_text=_('If selected the image\'s "view_count" will be incremented when this photo size is displayed.')) - effect = models.ForeignKey('PhotoEffect', null=True, blank=True, related_name='photo_sizes', verbose_name=_('photo effect')) - watermark = models.ForeignKey('Watermark', null=True, blank=True, related_name='photo_sizes', verbose_name=_('watermark image')) - - class Meta: - ordering = ['width', 'height'] - verbose_name = _('photo size') - verbose_name_plural = _('photo sizes') - - def __unicode__(self): - return self.name - - def __str__(self): - return self.__unicode__() - - def clear_cache(self): - for cls in ImageModel.__subclasses__(): - for obj in cls.objects.all(): - obj.remove_size(self) - if self.pre_cache: - obj.create_size(self) - PhotoSizeCache().reset() - - def save(self): - if self.width + self.height <= 0: - raise ValueError(_('A PhotoSize must have a positive height or width.')) - super(PhotoSize, self).save() - PhotoSizeCache().reset() - self.clear_cache() - - def delete(self): - self.clear_cache() - super(PhotoSize, self).delete() - - def _get_size(self): - return (self.width, self.height) - def _set_size(self, value): - self.width, self.height = value - size = property(_get_size, _set_size) - - -class PhotoSizeCache(object): - __state = {"sizes": {}} - - def __init__(self): - self.__dict__ = self.__state - if not len(self.sizes): - sizes = PhotoSize.objects.all() - for size in sizes: - self.sizes[size.name] = size - - def reset(self): - self.sizes = {} - - -# Set up the accessor methods -def add_methods(sender, instance, signal, *args, **kwargs): - """ Adds methods to access sized images (urls, paths) - - after the Photo model's __init__ function completes, - this method calls "add_accessor_methods" on each instance. - """ - if hasattr(instance, 'add_accessor_methods'): - instance.add_accessor_methods() - -# connect the add_accessor_methods function to the post_init signal -post_init.connect(add_methods)
--- a/mysite/photologue/templates/photologue/gallery_archive.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}Latest Photo Galleries{% endblock %} - -{% block content %} - -<h1>Latest Photo Galleries</h1> - -{% if latest %} - {% for gallery in latest %} - <div class="photo-gallery"> - <h2><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></h2> - {% for photo in gallery.sample|slice:sample_size %} - <div class="gallery-photo"> - <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> - </div> - {% endfor %} - </div> - {% endfor %} -{% else %} - <p>No galleries were found.</p> -{% endif %} - -<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> - -{% endblock %}
--- a/mysite/photologue/templates/photologue/gallery_archive_day.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}Galleries for {{ day|date }}{% endblock %} - -{% block content %} - -<h1>Galleries for {{ day|date }}</h1> - -{% if object_list %} - {% for gallery in object_list %} - <div class="photo-gallery"> - <h2>{{ gallery.title }}</h2> - {% for photo in gallery.sample|slice:sample_size %} - <div class="gallery-photo"> - <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> - </div> - {% endfor %} - </div> - {% endfor %} -{% else %} - <p>No galleries were found.</p> -{% endif %} - -<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> - -{% endblock %}
--- a/mysite/photologue/templates/photologue/gallery_archive_month.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}Galleries for {{ month|date:"F Y" }}{% endblock %} - -{% block content %} - -<h1>Galleries for {{ month|date:"F Y" }}</h1> - -{% if object_list %} - {% for gallery in object_list %} - <div class="photo-gallery"> - <h2>{{ gallery.title }}</h2> - {% for photo in gallery.sample|slice:sample_size %} - <div class="gallery-photo"> - <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> - </div> - {% endfor %} - </div> - {% endfor %} -{% else %} - <p>No galleries were found.</p> -{% endif %} - -<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> - -{% endblock %}
--- a/mysite/photologue/templates/photologue/gallery_archive_year.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}Galleries for {{ year }}{% endblock %} - -{% block content %} - -<h1>Galleries for {{ year }}</h1> -<ul> -{% for date in date_list %} -<li><a href="{{ date|date:"M"|lower }}/">{{ date|date:"F" }}</a></li> -{% endfor %} -</ul> - -<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> - -{% endblock %}
--- a/mysite/photologue/templates/photologue/gallery_detail.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}{{ object.title }}{% endblock %} - -{% block content %} - -<h1>{{ object.title }}</h1> -<h2>Originally published {{ object.date_added|date:"l, F jS, Y" }}</h2> -{% if object.description %}<p>{{ object.description }}</p>{% endif %} -<div class="photo-gallery"> - {% for photo in object.public %} - <div class="gallery-photo"> - <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> - </div> - {% endfor %} -</div> -<p><a href="{% url pl-gallery-list 1 %}">View all galleries</a></p> - -{% endblock %}
--- a/mysite/photologue/templates/photologue/gallery_list.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}All Galleries{% endblock %} - -{% block content %} - -<h1>All galleries</h1> - -{% if object_list %} - {% for gallery in object_list %} - <div class="photo-gallery"> - <h2><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></h2> - {% for photo in gallery.sample|slice:sample_size %} - <div class="gallery-photo"> - <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> - </div> - {% endfor %} - </div> - {% endfor %} -{% else %} - <p>No galleries were found.</p> -{% endif %} - -{% if is_paginated %} -<p>{{ hits }} galleries total.</p> -<div id="page_controls"> - <p>{% if has_previous %}<a href="{% url pl-gallery-list previous %}">Previous</a> | {% endif %} page {{ page }} of {{ pages }} {% if has_next %}| <a href="{% url pl-gallery-list next %}">Next</a>{% endif %}</p> -</div> -{% endif %} - -{% endblock %}
--- a/mysite/photologue/templates/photologue/photo_archive.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}Latest Photos{% endblock %} - -{% block content %} - -<h1>Latest Photos</h1> - -{% if latest %} - {% for photo in latest %} - <div class="gallery-photo"> - <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> - </div> - {% endfor %} -{% else %} -<p>No photos were found.</p> -{% endif %} -<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p> - -{% endblock %}
--- a/mysite/photologue/templates/photologue/photo_archive_day.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}Photos for {{ day|date }}{% endblock %} - -{% block content %} - -<h1>Photos for {{ day|date }}</h1> - -{% if object_list %} - {% for photo in object_list %} - <div class="gallery-photo"> - <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> - </div> - {% endfor %} -{% else %} -<p>No photos were found.</p> -{% endif %} -<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p> - -{% endblock %}
--- a/mysite/photologue/templates/photologue/photo_archive_month.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}Photos for {{ month|date:"F Y" }}{% endblock %} - -{% block content %} - -<h1>Photos for {{ month|date:"F Y" }}</h1> - -{% if object_list %} - {% for photo in object_list %} - <div class="gallery-photo"> - <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> - </div> - {% endfor %} -{% else %} -<p>No photos were found.</p> -{% endif %} -<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p> - -{% endblock %}
--- a/mysite/photologue/templates/photologue/photo_archive_year.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}Galleries for {{ year }}{% endblock %} - -{% block content %} - -<h1>Photos for {{ year }}</h1> -<ul> -{% for date in date_list %} -<li><a href="{{ date|date:"M"|lower }}/">{{ date|date:"F" }}</a></li> -{% endfor %} -</ul> - -{% endblock %}
--- a/mysite/photologue/templates/photologue/photo_detail.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}{{ object.title }}{% endblock %} - -{% block content %} - -<h1>{{ object.title }}</h1> -<div class="gallery-photo"> - <a href="{{ object.image.url }}"><img src="{{ object.get_display_url }}" alt="{{ object.title }}"/></a> - {% if object.caption %}<p>{{ object.caption }}</p>{% endif %} -</div> -{% if object.public_galleries %} -<h2>This photo is found in the following galleries:</h2> -<ol> -{% for gallery in object.public_galleries %} - <li><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></li> -{% endfor %} -</ol> -{% endif %} - -{% endblock %}
--- a/mysite/photologue/templates/photologue/photo_list.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -{% extends "photologue/root.html" %} - -{% block title %}All Photos{% endblock %} - -{% block content %} - -<h1>All Photos</h1> - -{% if object_list %} - {% for photo in object_list %} - <div class="gallery-photo"> - <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> - </div> - {% endfor %} -{% else %} -<p>No photos were found.</p> -{% endif %} - -{% if is_paginated %} -<p>{{ hits }} photos total.</p> -<div id="page_controls"> - <p>{% if has_previous %}<a href="{% url pl-photo-list previous %}">Previous</a> | {% endif %} page {{ page }} of {{ pages }} {% if has_next %}| <a href="{% url pl-photo-list next %}">Next</a>{% endif %}</p> -</div> -{% endif %} - -{% endblock %}
--- a/mysite/photologue/templates/photologue/root.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -{% extends "base.html" %} \ No newline at end of file
--- a/mysite/photologue/tests.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,218 +0,0 @@ -import os -import unittest -from django.conf import settings -from django.core.files.base import ContentFile -from django.test import TestCase - -from models import * - -# Path to sample image -RES_DIR = os.path.join(os.path.dirname(__file__), 'res') -LANDSCAPE_IMAGE_PATH = os.path.join(RES_DIR, 'test_landscape.jpg') -PORTRAIT_IMAGE_PATH = os.path.join(RES_DIR, 'test_portrait.jpg') -SQUARE_IMAGE_PATH = os.path.join(RES_DIR, 'test_square.jpg') - - -class TestPhoto(ImageModel): - """ Minimal ImageModel class for testing """ - name = models.CharField(max_length=30) - - -class PLTest(TestCase): - """ Base TestCase class """ - def setUp(self): - self.s = PhotoSize(name='test', width=100, height=100) - self.s.save() - self.pl = TestPhoto(name='landscape') - self.pl.image.save(os.path.basename(LANDSCAPE_IMAGE_PATH), - ContentFile(open(LANDSCAPE_IMAGE_PATH, 'rb').read())) - self.pl.save() - - def tearDown(self): - path = self.pl.image.path - self.pl.delete() - self.failIf(os.path.isfile(path)) - self.s.delete() - - -class PhotoTest(PLTest): - def test_new_photo(self): - self.assertEqual(TestPhoto.objects.count(), 1) - self.failUnless(os.path.isfile(self.pl.image.path)) - self.assertEqual(os.path.getsize(self.pl.image.path), - os.path.getsize(LANDSCAPE_IMAGE_PATH)) - - def test_exif(self): - self.assert_(len(self.pl.EXIF.keys()) > 0) - - def test_paths(self): - self.assertEqual(os.path.normpath(str(self.pl.cache_path())).lower(), - os.path.normpath(os.path.join(settings.MEDIA_ROOT, - PHOTOLOGUE_DIR, - 'photos', - 'cache')).lower()) - self.assertEqual(self.pl.cache_url(), - settings.MEDIA_URL + PHOTOLOGUE_DIR + '/photos/cache') - - def test_count(self): - for i in range(5): - self.pl.get_test_url() - self.assertEquals(self.pl.view_count, 0) - self.s.increment_count = True - self.s.save() - for i in range(5): - self.pl.get_test_url() - self.assertEquals(self.pl.view_count, 5) - - def test_precache(self): - # set the thumbnail photo size to pre-cache - self.s.pre_cache = True - self.s.save() - # make sure it created the file - self.failUnless(os.path.isfile(self.pl.get_test_filename())) - self.s.pre_cache = False - self.s.save() - # clear the cache and make sure the file's deleted - self.pl.clear_cache() - self.failIf(os.path.isfile(self.pl.get_test_filename())) - - def test_accessor_methods(self): - self.assertEquals(self.pl.get_test_photosize(), self.s) - self.assertEquals(self.pl.get_test_size(), - Image.open(self.pl.get_test_filename()).size) - self.assertEquals(self.pl.get_test_url(), - self.pl.cache_url() + '/' + \ - self.pl._get_filename_for_size(self.s)) - self.assertEquals(self.pl.get_test_filename(), - os.path.join(self.pl.cache_path(), - self.pl._get_filename_for_size(self.s))) - - -class ImageResizeTest(PLTest): - def setUp(self): - super(ImageResizeTest, self).setUp() - self.pp = TestPhoto(name='portrait') - self.pp.image.save(os.path.basename(PORTRAIT_IMAGE_PATH), - ContentFile(open(PORTRAIT_IMAGE_PATH, 'rb').read())) - self.pp.save() - self.ps = TestPhoto(name='square') - self.ps.image.save(os.path.basename(SQUARE_IMAGE_PATH), - ContentFile(open(SQUARE_IMAGE_PATH, 'rb').read())) - self.ps.save() - - def tearDown(self): - super(ImageResizeTest, self).tearDown() - self.pp.delete() - self.ps.delete() - - def test_resize_to_fit(self): - self.assertEquals(self.pl.get_test_size(), (100, 75)) - self.assertEquals(self.pp.get_test_size(), (75, 100)) - self.assertEquals(self.ps.get_test_size(), (100, 100)) - - def test_resize_to_fit_width(self): - self.s.size = (100, 0) - self.s.save() - self.assertEquals(self.pl.get_test_size(), (100, 75)) - self.assertEquals(self.pp.get_test_size(), (100, 133)) - self.assertEquals(self.ps.get_test_size(), (100, 100)) - - def test_resize_to_fit_width_enlarge(self): - self.s.size = (2000, 0) - self.s.upscale = True - self.s.save() - self.assertEquals(self.pl.get_test_size(), (2000, 1500)) - self.assertEquals(self.pp.get_test_size(), (2000, 2667)) - self.assertEquals(self.ps.get_test_size(), (2000, 2000)) - - def test_resize_to_fit_height(self): - self.s.size = (0, 100) - self.s.save() - self.assertEquals(self.pl.get_test_size(), (133, 100)) - self.assertEquals(self.pp.get_test_size(), (75, 100)) - self.assertEquals(self.ps.get_test_size(), (100, 100)) - - def test_resize_to_fit_height_enlarge(self): - self.s.size = (0, 2000) - self.s.upscale = True - self.s.save() - self.assertEquals(self.pl.get_test_size(), (2667, 2000)) - self.assertEquals(self.pp.get_test_size(), (1500, 2000)) - self.assertEquals(self.ps.get_test_size(), (2000, 2000)) - - def test_resize_and_crop(self): - self.s.crop = True - self.s.save() - self.assertEquals(self.pl.get_test_size(), self.s.size) - self.assertEquals(self.pp.get_test_size(), self.s.size) - self.assertEquals(self.ps.get_test_size(), self.s.size) - - def test_resize_rounding_to_fit(self): - self.s.size = (113, 113) - self.s.save() - self.assertEquals(self.pl.get_test_size(), (113, 85)) - self.assertEquals(self.pp.get_test_size(), (85, 113)) - self.assertEquals(self.ps.get_test_size(), (113, 113)) - - def test_resize_rounding_cropped(self): - self.s.size = (113, 113) - self.s.crop = True - self.s.save() - self.assertEquals(self.pl.get_test_size(), self.s.size) - self.assertEquals(self.pp.get_test_size(), self.s.size) - self.assertEquals(self.ps.get_test_size(), self.s.size) - - def test_resize_one_dimension_width(self): - self.s.size = (1500, 1200) - self.s.save() - self.assertEquals(self.pl.get_test_size(), (1500, 1125)) - - def test_resize_one_dimension_height(self): - self.s.size = (1600, 1100) - self.s.save() - self.assertEquals(self.pl.get_test_size(), (1467, 1100)) - - def test_resize_no_upscale(self): - self.s.size = (2000, 2000) - self.s.save() - self.assertEquals(self.pl.get_test_size(), (1600, 1200)) - - def test_resize_no_upscale_mixed_height(self): - self.s.size = (3200, 600) - self.s.save() - self.assertEquals(self.pl.get_test_size(), (800, 600)) - - def test_resize_no_upscale_mixed_width(self): - self.s.size = (800, 2400) - self.s.save() - self.assertEquals(self.pl.get_test_size(), (800, 600)) - - def test_resize_no_upscale_crop(self): - self.s.size = (2000, 2000) - self.s.crop = True - self.s.save() - self.assertEquals(self.pl.get_test_size(), (2000, 2000)) - - def test_resize_upscale(self): - self.s.size = (2000, 2000) - self.s.upscale = True - self.s.save() - self.assertEquals(self.pl.get_test_size(), (2000, 1500)) - self.assertEquals(self.pp.get_test_size(), (1500, 2000)) - self.assertEquals(self.ps.get_test_size(), (2000, 2000)) - - -class PhotoEffectTest(PLTest): - def test(self): - effect = PhotoEffect(name='test') - im = Image.open(self.pl.image.path) - self.assert_(isinstance(effect.pre_process(im), Image.Image)) - self.assert_(isinstance(effect.post_process(im), Image.Image)) - self.assert_(isinstance(effect.process(im), Image.Image)) - - -class PhotoSizeCacheTest(PLTest): - def test(self): - cache = PhotoSizeCache() - self.assertEqual(cache.sizes['test'], self.s) -
--- a/mysite/photologue/urls.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -from django.conf import settings -from django.conf.urls.defaults import * -from models import * - -# Number of random images from the gallery to display. -SAMPLE_SIZE = ":%s" % getattr(settings, 'GALLERY_SAMPLE_SIZE', 5) - -# galleries -gallery_args = {'date_field': 'date_added', 'allow_empty': True, 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}} -urlpatterns = patterns('django.views.generic.date_based', - url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'date_field': 'date_added', 'slug_field': 'title_slug', 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery-detail'), - url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', gallery_args, name='pl-gallery-archive-day'), - url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', gallery_args, name='pl-gallery-archive-month'), - url(r'^gallery/(?P<year>\d{4})/$', 'archive_year', gallery_args, name='pl-gallery-archive-year'), - url(r'^gallery/?$', 'archive_index', gallery_args, name='pl-gallery-archive'), -) -urlpatterns += patterns('django.views.generic.list_detail', - url(r'^gallery/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'slug_field': 'title_slug', 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery'), - url(r'^gallery/page/(?P<page>[0-9]+)/$', 'object_list', {'queryset': Gallery.objects.filter(is_public=True), 'allow_empty': True, 'paginate_by': 5, 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery-list'), -) - -# photographs -photo_args = {'date_field': 'date_added', 'allow_empty': True, 'queryset': Photo.objects.filter(is_public=True)} -urlpatterns += patterns('django.views.generic.date_based', - url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'date_field': 'date_added', 'slug_field': 'title_slug', 'queryset': Photo.objects.filter(is_public=True)}, name='pl-photo-detail'), - url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', photo_args, name='pl-photo-archive-day'), - url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', photo_args, name='pl-photo-archive-month'), - url(r'^photo/(?P<year>\d{4})/$', 'archive_year', photo_args, name='pl-photo-archive-year'), - url(r'^photo/$', 'archive_index', photo_args, name='pl-photo-archive'), -) -urlpatterns += patterns('django.views.generic.list_detail', - url(r'^photo/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'slug_field': 'title_slug', 'queryset': Photo.objects.filter(is_public=True)}, name='pl-photo'), - url(r'^photo/page/(?P<page>[0-9]+)/$', 'object_list', {'queryset': Photo.objects.filter(is_public=True), 'allow_empty': True, 'paginate_by': 20}, name='pl-photo-list'), -) - -
--- a/mysite/photologue/utils/EXIF.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1568 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Library to extract EXIF information from digital camera image files -# http://sourceforge.net/projects/exif-py/ -# -# VERSION 1.0.7 -# -# To use this library call with: -# f = open(path_name, 'rb') -# tags = EXIF.process_file(f) -# -# To ignore makerNote tags, pass the -q or --quick -# command line arguments, or as -# f = open(path_name, 'rb') -# tags = EXIF.process_file(f, details=False) -# -# To stop processing after a certain tag is retrieved, -# pass the -t TAG or --stop-tag TAG argument, or as -# f = open(path_name, 'rb') -# tags = EXIF.process_file(f, stop_tag='TAG') -# -# where TAG is a valid tag name, ex 'DateTimeOriginal' -# -# These are useful when you are retrieving a large list of images -# -# Returned tags will be a dictionary mapping names of EXIF tags to their -# values in the file named by path_name. You can process the tags -# as you wish. In particular, you can iterate through all the tags with: -# for tag in tags.keys(): -# if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', -# 'EXIF MakerNote'): -# print "Key: %s, value %s" % (tag, tags[tag]) -# (This code uses the if statement to avoid printing out a few of the -# tags that tend to be long or boring.) -# -# The tags dictionary will include keys for all of the usual EXIF -# tags, and will also include keys for Makernotes used by some -# cameras, for which we have a good specification. -# -# Note that the dictionary keys are the IFD name followed by the -# tag name. For example: -# 'EXIF DateTimeOriginal', 'Image Orientation', 'MakerNote FocusMode' -# -# Copyright (c) 2002-2007 Gene Cash All rights reserved -# Copyright (c) 2007 Ianaré Sévi All rights reserved -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# -# 3. Neither the name of the authors nor the names of its contributors -# may be used to endorse or promote products derived from this -# software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# -# ----- See 'changes.txt' file for all contributors and changes ----- # -# - - -# Don't throw an exception when given an out of range character. -def make_string(seq): - str = "" - for c in seq: - # Screen out non-printing characters - if 32 <= c and c < 256: - str += chr(c) - # If no printing chars - if not str: - return seq - return str - -# Special version to deal with the code in the first 8 bytes of a user comment. -def make_string_uc(seq): - code = seq[0:8] - seq = seq[8:] - # Of course, this is only correct if ASCII, and the standard explicitly - # allows JIS and Unicode. - return make_string(seq) - -# field type descriptions as (length, abbreviation, full name) tuples -FIELD_TYPES = ( - (0, 'X', 'Proprietary'), # no such type - (1, 'B', 'Byte'), - (1, 'A', 'ASCII'), - (2, 'S', 'Short'), - (4, 'L', 'Long'), - (8, 'R', 'Ratio'), - (1, 'SB', 'Signed Byte'), - (1, 'U', 'Undefined'), - (2, 'SS', 'Signed Short'), - (4, 'SL', 'Signed Long'), - (8, 'SR', 'Signed Ratio'), - ) - -# dictionary of main EXIF tag names -# first element of tuple is tag name, optional second element is -# another dictionary giving names to values -EXIF_TAGS = { - 0x0100: ('ImageWidth', ), - 0x0101: ('ImageLength', ), - 0x0102: ('BitsPerSample', ), - 0x0103: ('Compression', - {1: 'Uncompressed TIFF', - 6: 'JPEG Compressed'}), - 0x0106: ('PhotometricInterpretation', ), - 0x010A: ('FillOrder', ), - 0x010D: ('DocumentName', ), - 0x010E: ('ImageDescription', ), - 0x010F: ('Make', ), - 0x0110: ('Model', ), - 0x0111: ('StripOffsets', ), - 0x0112: ('Orientation', - {1: 'Horizontal (normal)', - 2: 'Mirrored horizontal', - 3: 'Rotated 180', - 4: 'Mirrored vertical', - 5: 'Mirrored horizontal then rotated 90 CCW', - 6: 'Rotated 90 CW', - 7: 'Mirrored horizontal then rotated 90 CW', - 8: 'Rotated 90 CCW'}), - 0x0115: ('SamplesPerPixel', ), - 0x0116: ('RowsPerStrip', ), - 0x0117: ('StripByteCounts', ), - 0x011A: ('XResolution', ), - 0x011B: ('YResolution', ), - 0x011C: ('PlanarConfiguration', ), - 0x0128: ('ResolutionUnit', - {1: 'Not Absolute', - 2: 'Pixels/Inch', - 3: 'Pixels/Centimeter'}), - 0x012D: ('TransferFunction', ), - 0x0131: ('Software', ), - 0x0132: ('DateTime', ), - 0x013B: ('Artist', ), - 0x013E: ('WhitePoint', ), - 0x013F: ('PrimaryChromaticities', ), - 0x0156: ('TransferRange', ), - 0x0200: ('JPEGProc', ), - 0x0201: ('JPEGInterchangeFormat', ), - 0x0202: ('JPEGInterchangeFormatLength', ), - 0x0211: ('YCbCrCoefficients', ), - 0x0212: ('YCbCrSubSampling', ), - 0x0213: ('YCbCrPositioning', ), - 0x0214: ('ReferenceBlackWhite', ), - 0x828D: ('CFARepeatPatternDim', ), - 0x828E: ('CFAPattern', ), - 0x828F: ('BatteryLevel', ), - 0x8298: ('Copyright', ), - 0x829A: ('ExposureTime', ), - 0x829D: ('FNumber', ), - 0x83BB: ('IPTC/NAA', ), - 0x8769: ('ExifOffset', ), - 0x8773: ('InterColorProfile', ), - 0x8822: ('ExposureProgram', - {0: 'Unidentified', - 1: 'Manual', - 2: 'Program Normal', - 3: 'Aperture Priority', - 4: 'Shutter Priority', - 5: 'Program Creative', - 6: 'Program Action', - 7: 'Portrait Mode', - 8: 'Landscape Mode'}), - 0x8824: ('SpectralSensitivity', ), - 0x8825: ('GPSInfo', ), - 0x8827: ('ISOSpeedRatings', ), - 0x8828: ('OECF', ), - # print as string - 0x9000: ('ExifVersion', make_string), - 0x9003: ('DateTimeOriginal', ), - 0x9004: ('DateTimeDigitized', ), - 0x9101: ('ComponentsConfiguration', - {0: '', - 1: 'Y', - 2: 'Cb', - 3: 'Cr', - 4: 'Red', - 5: 'Green', - 6: 'Blue'}), - 0x9102: ('CompressedBitsPerPixel', ), - 0x9201: ('ShutterSpeedValue', ), - 0x9202: ('ApertureValue', ), - 0x9203: ('BrightnessValue', ), - 0x9204: ('ExposureBiasValue', ), - 0x9205: ('MaxApertureValue', ), - 0x9206: ('SubjectDistance', ), - 0x9207: ('MeteringMode', - {0: 'Unidentified', - 1: 'Average', - 2: 'CenterWeightedAverage', - 3: 'Spot', - 4: 'MultiSpot'}), - 0x9208: ('LightSource', - {0: 'Unknown', - 1: 'Daylight', - 2: 'Fluorescent', - 3: 'Tungsten', - 10: 'Flash', - 17: 'Standard Light A', - 18: 'Standard Light B', - 19: 'Standard Light C', - 20: 'D55', - 21: 'D65', - 22: 'D75', - 255: 'Other'}), - 0x9209: ('Flash', {0: 'No', - 1: 'Fired', - 5: 'Fired (?)', # no return sensed - 7: 'Fired (!)', # return sensed - 9: 'Fill Fired', - 13: 'Fill Fired (?)', - 15: 'Fill Fired (!)', - 16: 'Off', - 24: 'Auto Off', - 25: 'Auto Fired', - 29: 'Auto Fired (?)', - 31: 'Auto Fired (!)', - 32: 'Not Available'}), - 0x920A: ('FocalLength', ), - 0x9214: ('SubjectArea', ), - 0x927C: ('MakerNote', ), - # print as string - 0x9286: ('UserComment', make_string_uc), # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode - 0x9290: ('SubSecTime', ), - 0x9291: ('SubSecTimeOriginal', ), - 0x9292: ('SubSecTimeDigitized', ), - # print as string - 0xA000: ('FlashPixVersion', make_string), - 0xA001: ('ColorSpace', ), - 0xA002: ('ExifImageWidth', ), - 0xA003: ('ExifImageLength', ), - 0xA005: ('InteroperabilityOffset', ), - 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP - 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C - - - 0xA20E: ('FocalPlaneXResolution', ), # 0x920E - - - 0xA20F: ('FocalPlaneYResolution', ), # 0x920F - - - 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 - - - 0xA214: ('SubjectLocation', ), # 0x9214 - - - 0xA215: ('ExposureIndex', ), # 0x9215 - - - 0xA217: ('SensingMethod', ), # 0x9217 - - - 0xA300: ('FileSource', - {3: 'Digital Camera'}), - 0xA301: ('SceneType', - {1: 'Directly Photographed'}), - 0xA302: ('CVAPattern', ), - 0xA401: ('CustomRendered', ), - 0xA402: ('ExposureMode', - {0: 'Auto Exposure', - 1: 'Manual Exposure', - 2: 'Auto Bracket'}), - 0xA403: ('WhiteBalance', - {0: 'Auto', - 1: 'Manual'}), - 0xA404: ('DigitalZoomRatio', ), - 0xA405: ('FocalLengthIn35mm', ), - 0xA406: ('SceneCaptureType', ), - 0xA407: ('GainControl', ), - 0xA408: ('Contrast', ), - 0xA409: ('Saturation', ), - 0xA40A: ('Sharpness', ), - 0xA40C: ('SubjectDistanceRange', ), - } - -# interoperability tags -INTR_TAGS = { - 0x0001: ('InteroperabilityIndex', ), - 0x0002: ('InteroperabilityVersion', ), - 0x1000: ('RelatedImageFileFormat', ), - 0x1001: ('RelatedImageWidth', ), - 0x1002: ('RelatedImageLength', ), - } - -# GPS tags (not used yet, haven't seen camera with GPS) -GPS_TAGS = { - 0x0000: ('GPSVersionID', ), - 0x0001: ('GPSLatitudeRef', ), - 0x0002: ('GPSLatitude', ), - 0x0003: ('GPSLongitudeRef', ), - 0x0004: ('GPSLongitude', ), - 0x0005: ('GPSAltitudeRef', ), - 0x0006: ('GPSAltitude', ), - 0x0007: ('GPSTimeStamp', ), - 0x0008: ('GPSSatellites', ), - 0x0009: ('GPSStatus', ), - 0x000A: ('GPSMeasureMode', ), - 0x000B: ('GPSDOP', ), - 0x000C: ('GPSSpeedRef', ), - 0x000D: ('GPSSpeed', ), - 0x000E: ('GPSTrackRef', ), - 0x000F: ('GPSTrack', ), - 0x0010: ('GPSImgDirectionRef', ), - 0x0011: ('GPSImgDirection', ), - 0x0012: ('GPSMapDatum', ), - 0x0013: ('GPSDestLatitudeRef', ), - 0x0014: ('GPSDestLatitude', ), - 0x0015: ('GPSDestLongitudeRef', ), - 0x0016: ('GPSDestLongitude', ), - 0x0017: ('GPSDestBearingRef', ), - 0x0018: ('GPSDestBearing', ), - 0x0019: ('GPSDestDistanceRef', ), - 0x001A: ('GPSDestDistance', ), - } - -# Ignore these tags when quick processing -# 0x927C is MakerNote Tags -# 0x9286 is user comment -IGNORE_TAGS=(0x9286, 0x927C) - -# http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp -def nikon_ev_bias(seq): - # First digit seems to be in steps of 1/6 EV. - # Does the third value mean the step size? It is usually 6, - # but it is 12 for the ExposureDifference. - # - if seq == [252, 1, 6, 0]: - return "-2/3 EV" - if seq == [253, 1, 6, 0]: - return "-1/2 EV" - if seq == [254, 1, 6, 0]: - return "-1/3 EV" - if seq == [0, 1, 6, 0]: - return "0 EV" - if seq == [2, 1, 6, 0]: - return "+1/3 EV" - if seq == [3, 1, 6, 0]: - return "+1/2 EV" - if seq == [4, 1, 6, 0]: - return "+2/3 EV" - # Handle combinations not in the table. - a = seq[0] - # Causes headaches for the +/- logic, so special case it. - if a == 0: - return "0 EV" - if a > 127: - a = 256 - a - ret_str = "-" - else: - ret_str = "+" - b = seq[2] # Assume third value means the step size - whole = a / b - a = a % b - if whole != 0: - ret_str = ret_str + str(whole) + " " - if a == 0: - ret_str = ret_str + "EV" - else: - r = Ratio(a, b) - ret_str = ret_str + r.__repr__() + " EV" - return ret_str - -# Nikon E99x MakerNote Tags -MAKERNOTE_NIKON_NEWER_TAGS={ - 0x0001: ('MakernoteVersion', make_string), # Sometimes binary - 0x0002: ('ISOSetting', ), - 0x0003: ('ColorMode', ), - 0x0004: ('Quality', ), - 0x0005: ('Whitebalance', ), - 0x0006: ('ImageSharpening', ), - 0x0007: ('FocusMode', ), - 0x0008: ('FlashSetting', ), - 0x0009: ('AutoFlashMode', ), - 0x000B: ('WhiteBalanceBias', ), - 0x000C: ('WhiteBalanceRBCoeff', ), - 0x000D: ('ProgramShift', nikon_ev_bias), - # Nearly the same as the other EV vals, but step size is 1/12 EV (?) - 0x000E: ('ExposureDifference', nikon_ev_bias), - 0x000F: ('ISOSelection', ), - 0x0011: ('NikonPreview', ), - 0x0012: ('FlashCompensation', nikon_ev_bias), - 0x0013: ('ISOSpeedRequested', ), - 0x0016: ('PhotoCornerCoordinates', ), - # 0x0017: Unknown, but most likely an EV value - 0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias), - 0x0019: ('AEBracketCompensationApplied', ), - 0x001A: ('ImageProcessing', ), - 0x0080: ('ImageAdjustment', ), - 0x0081: ('ToneCompensation', ), - 0x0082: ('AuxiliaryLens', ), - 0x0083: ('LensType', ), - 0x0084: ('LensMinMaxFocalMaxAperture', ), - 0x0085: ('ManualFocusDistance', ), - 0x0086: ('DigitalZoomFactor', ), - 0x0087: ('FlashMode', - {0x00: 'Did Not Fire', - 0x01: 'Fired, Manual', - 0x07: 'Fired, External', - 0x08: 'Fired, Commander Mode ', - 0x09: 'Fired, TTL Mode'}), - 0x0088: ('AFFocusPosition', - {0x0000: 'Center', - 0x0100: 'Top', - 0x0200: 'Bottom', - 0x0300: 'Left', - 0x0400: 'Right'}), - 0x0089: ('BracketingMode', - {0x00: 'Single frame, no bracketing', - 0x01: 'Continuous, no bracketing', - 0x02: 'Timer, no bracketing', - 0x10: 'Single frame, exposure bracketing', - 0x11: 'Continuous, exposure bracketing', - 0x12: 'Timer, exposure bracketing', - 0x40: 'Single frame, white balance bracketing', - 0x41: 'Continuous, white balance bracketing', - 0x42: 'Timer, white balance bracketing'}), - 0x008A: ('AutoBracketRelease', ), - 0x008B: ('LensFStops', ), - 0x008C: ('NEFCurve2', ), - 0x008D: ('ColorMode', ), - 0x008F: ('SceneMode', ), - 0x0090: ('LightingType', ), - 0x0091: ('ShotInfo', ), # First 4 bytes are probably a version number in ASCII - 0x0092: ('HueAdjustment', ), - # 0x0093: ('SaturationAdjustment', ), - 0x0094: ('Saturation', # Name conflict with 0x00AA !! - {-3: 'B&W', - -2: '-2', - -1: '-1', - 0: '0', - 1: '1', - 2: '2'}), - 0x0095: ('NoiseReduction', ), - 0x0096: ('NEFCurve2', ), - 0x0097: ('ColorBalance', ), - 0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII - 0x0099: ('RawImageCenter', ), - 0x009A: ('SensorPixelSize', ), - 0x009C: ('Scene Assist', ), - 0x00A0: ('SerialNumber', ), - 0x00A2: ('ImageDataSize', ), - # A4: In NEF, looks like a 4 byte ASCII version number - 0x00A5: ('ImageCount', ), - 0x00A6: ('DeletedImageCount', ), - 0x00A7: ('TotalShutterReleases', ), - # A8: ExposureMode? JPG: First 4 bytes are probably a version number in ASCII - # But in a sample NEF, its 8 zeros, then the string "NORMAL" - 0x00A9: ('ImageOptimization', ), - 0x00AA: ('Saturation', ), - 0x00AB: ('DigitalVariProgram', ), - 0x00AC: ('ImageStabilization', ), - 0x00AD: ('Responsive AF', ), # 'AFResponse' - 0x0010: ('DataDump', ), - } - -MAKERNOTE_NIKON_OLDER_TAGS = { - 0x0003: ('Quality', - {1: 'VGA Basic', - 2: 'VGA Normal', - 3: 'VGA Fine', - 4: 'SXGA Basic', - 5: 'SXGA Normal', - 6: 'SXGA Fine'}), - 0x0004: ('ColorMode', - {1: 'Color', - 2: 'Monochrome'}), - 0x0005: ('ImageAdjustment', - {0: 'Normal', - 1: 'Bright+', - 2: 'Bright-', - 3: 'Contrast+', - 4: 'Contrast-'}), - 0x0006: ('CCDSpeed', - {0: 'ISO 80', - 2: 'ISO 160', - 4: 'ISO 320', - 5: 'ISO 100'}), - 0x0007: ('WhiteBalance', - {0: 'Auto', - 1: 'Preset', - 2: 'Daylight', - 3: 'Incandescent', - 4: 'Fluorescent', - 5: 'Cloudy', - 6: 'Speed Light'}), - } - -# decode Olympus SpecialMode tag in MakerNote -def olympus_special_mode(v): - a={ - 0: 'Normal', - 1: 'Unknown', - 2: 'Fast', - 3: 'Panorama'} - b={ - 0: 'Non-panoramic', - 1: 'Left to right', - 2: 'Right to left', - 3: 'Bottom to top', - 4: 'Top to bottom'} - if v[0] not in a or v[2] not in b: - return v - return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]]) - -MAKERNOTE_OLYMPUS_TAGS={ - # ah HAH! those sneeeeeaky bastids! this is how they get past the fact - # that a JPEG thumbnail is not allowed in an uncompressed TIFF file - 0x0100: ('JPEGThumbnail', ), - 0x0200: ('SpecialMode', olympus_special_mode), - 0x0201: ('JPEGQual', - {1: 'SQ', - 2: 'HQ', - 3: 'SHQ'}), - 0x0202: ('Macro', - {0: 'Normal', - 1: 'Macro', - 2: 'SuperMacro'}), - 0x0203: ('BWMode', - {0: 'Off', - 1: 'On'}), - 0x0204: ('DigitalZoom', ), - 0x0205: ('FocalPlaneDiagonal', ), - 0x0206: ('LensDistortionParams', ), - 0x0207: ('SoftwareRelease', ), - 0x0208: ('PictureInfo', ), - 0x0209: ('CameraID', make_string), # print as string - 0x0F00: ('DataDump', ), - 0x0300: ('PreCaptureFrames', ), - 0x0404: ('SerialNumber', ), - 0x1000: ('ShutterSpeedValue', ), - 0x1001: ('ISOValue', ), - 0x1002: ('ApertureValue', ), - 0x1003: ('BrightnessValue', ), - 0x1004: ('FlashMode', ), - 0x1004: ('FlashMode', - {2: 'On', - 3: 'Off'}), - 0x1005: ('FlashDevice', - {0: 'None', - 1: 'Internal', - 4: 'External', - 5: 'Internal + External'}), - 0x1006: ('ExposureCompensation', ), - 0x1007: ('SensorTemperature', ), - 0x1008: ('LensTemperature', ), - 0x100b: ('FocusMode', - {0: 'Auto', - 1: 'Manual'}), - 0x1017: ('RedBalance', ), - 0x1018: ('BlueBalance', ), - 0x101a: ('SerialNumber', ), - 0x1023: ('FlashExposureComp', ), - 0x1026: ('ExternalFlashBounce', - {0: 'No', - 1: 'Yes'}), - 0x1027: ('ExternalFlashZoom', ), - 0x1028: ('ExternalFlashMode', ), - 0x1029: ('Contrast int16u', - {0: 'High', - 1: 'Normal', - 2: 'Low'}), - 0x102a: ('SharpnessFactor', ), - 0x102b: ('ColorControl', ), - 0x102c: ('ValidBits', ), - 0x102d: ('CoringFilter', ), - 0x102e: ('OlympusImageWidth', ), - 0x102f: ('OlympusImageHeight', ), - 0x1034: ('CompressionRatio', ), - 0x1035: ('PreviewImageValid', - {0: 'No', - 1: 'Yes'}), - 0x1036: ('PreviewImageStart', ), - 0x1037: ('PreviewImageLength', ), - 0x1039: ('CCDScanMode', - {0: 'Interlaced', - 1: 'Progressive'}), - 0x103a: ('NoiseReduction', - {0: 'Off', - 1: 'On'}), - 0x103b: ('InfinityLensStep', ), - 0x103c: ('NearLensStep', ), - - # TODO - these need extra definitions - # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html - 0x2010: ('Equipment', ), - 0x2020: ('CameraSettings', ), - 0x2030: ('RawDevelopment', ), - 0x2040: ('ImageProcessing', ), - 0x2050: ('FocusInfo', ), - 0x3000: ('RawInfo ', ), - } - -# 0x2020 CameraSettings -MAKERNOTE_OLYMPUS_TAG_0x2020={ - 0x0100: ('PreviewImageValid', - {0: 'No', - 1: 'Yes'}), - 0x0101: ('PreviewImageStart', ), - 0x0102: ('PreviewImageLength', ), - 0x0200: ('ExposureMode', { - 1: 'Manual', - 2: 'Program', - 3: 'Aperture-priority AE', - 4: 'Shutter speed priority AE', - 5: 'Program-shift'}), - 0x0201: ('AELock', - {0: 'Off', - 1: 'On'}), - 0x0202: ('MeteringMode', - {2: 'Center Weighted', - 3: 'Spot', - 5: 'ESP', - 261: 'Pattern+AF', - 515: 'Spot+Highlight control', - 1027: 'Spot+Shadow control'}), - 0x0300: ('MacroMode', - {0: 'Off', - 1: 'On'}), - 0x0301: ('FocusMode', - {0: 'Single AF', - 1: 'Sequential shooting AF', - 2: 'Continuous AF', - 3: 'Multi AF', - 10: 'MF'}), - 0x0302: ('FocusProcess', - {0: 'AF Not Used', - 1: 'AF Used'}), - 0x0303: ('AFSearch', - {0: 'Not Ready', - 1: 'Ready'}), - 0x0304: ('AFAreas', ), - 0x0401: ('FlashExposureCompensation', ), - 0x0500: ('WhiteBalance2', - {0: 'Auto', - 16: '7500K (Fine Weather with Shade)', - 17: '6000K (Cloudy)', - 18: '5300K (Fine Weather)', - 20: '3000K (Tungsten light)', - 21: '3600K (Tungsten light-like)', - 33: '6600K (Daylight fluorescent)', - 34: '4500K (Neutral white fluorescent)', - 35: '4000K (Cool white fluorescent)', - 48: '3600K (Tungsten light-like)', - 256: 'Custom WB 1', - 257: 'Custom WB 2', - 258: 'Custom WB 3', - 259: 'Custom WB 4', - 512: 'Custom WB 5400K', - 513: 'Custom WB 2900K', - 514: 'Custom WB 8000K', }), - 0x0501: ('WhiteBalanceTemperature', ), - 0x0502: ('WhiteBalanceBracket', ), - 0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max) - 0x0504: ('ModifiedSaturation', - {0: 'Off', - 1: 'CM1 (Red Enhance)', - 2: 'CM2 (Green Enhance)', - 3: 'CM3 (Blue Enhance)', - 4: 'CM4 (Skin Tones)'}), - 0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max) - 0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max) - 0x0507: ('ColorSpace', - {0: 'sRGB', - 1: 'Adobe RGB', - 2: 'Pro Photo RGB'}), - 0x0509: ('SceneMode', - {0: 'Standard', - 6: 'Auto', - 7: 'Sport', - 8: 'Portrait', - 9: 'Landscape+Portrait', - 10: 'Landscape', - 11: 'Night scene', - 13: 'Panorama', - 16: 'Landscape+Portrait', - 17: 'Night+Portrait', - 19: 'Fireworks', - 20: 'Sunset', - 22: 'Macro', - 25: 'Documents', - 26: 'Museum', - 28: 'Beach&Snow', - 30: 'Candle', - 35: 'Underwater Wide1', - 36: 'Underwater Macro', - 39: 'High Key', - 40: 'Digital Image Stabilization', - 44: 'Underwater Wide2', - 45: 'Low Key', - 46: 'Children', - 48: 'Nature Macro'}), - 0x050a: ('NoiseReduction', - {0: 'Off', - 1: 'Noise Reduction', - 2: 'Noise Filter', - 3: 'Noise Reduction + Noise Filter', - 4: 'Noise Filter (ISO Boost)', - 5: 'Noise Reduction + Noise Filter (ISO Boost)'}), - 0x050b: ('DistortionCorrection', - {0: 'Off', - 1: 'On'}), - 0x050c: ('ShadingCompensation', - {0: 'Off', - 1: 'On'}), - 0x050d: ('CompressionFactor', ), - 0x050f: ('Gradation', - {'-1 -1 1': 'Low Key', - '0 -1 1': 'Normal', - '1 -1 1': 'High Key'}), - 0x0520: ('PictureMode', - {1: 'Vivid', - 2: 'Natural', - 3: 'Muted', - 256: 'Monotone', - 512: 'Sepia'}), - 0x0521: ('PictureModeSaturation', ), - 0x0522: ('PictureModeHue?', ), - 0x0523: ('PictureModeContrast', ), - 0x0524: ('PictureModeSharpness', ), - 0x0525: ('PictureModeBWFilter', - {0: 'n/a', - 1: 'Neutral', - 2: 'Yellow', - 3: 'Orange', - 4: 'Red', - 5: 'Green'}), - 0x0526: ('PictureModeTone', - {0: 'n/a', - 1: 'Neutral', - 2: 'Sepia', - 3: 'Blue', - 4: 'Purple', - 5: 'Green'}), - 0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits - 0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number) - 0x0603: ('ImageQuality2', - {1: 'SQ', - 2: 'HQ', - 3: 'SHQ', - 4: 'RAW'}), - 0x0901: ('ManometerReading', ), - } - - -MAKERNOTE_CASIO_TAGS={ - 0x0001: ('RecordingMode', - {1: 'Single Shutter', - 2: 'Panorama', - 3: 'Night Scene', - 4: 'Portrait', - 5: 'Landscape'}), - 0x0002: ('Quality', - {1: 'Economy', - 2: 'Normal', - 3: 'Fine'}), - 0x0003: ('FocusingMode', - {2: 'Macro', - 3: 'Auto Focus', - 4: 'Manual Focus', - 5: 'Infinity'}), - 0x0004: ('FlashMode', - {1: 'Auto', - 2: 'On', - 3: 'Off', - 4: 'Red Eye Reduction'}), - 0x0005: ('FlashIntensity', - {11: 'Weak', - 13: 'Normal', - 15: 'Strong'}), - 0x0006: ('Object Distance', ), - 0x0007: ('WhiteBalance', - {1: 'Auto', - 2: 'Tungsten', - 3: 'Daylight', - 4: 'Fluorescent', - 5: 'Shade', - 129: 'Manual'}), - 0x000B: ('Sharpness', - {0: 'Normal', - 1: 'Soft', - 2: 'Hard'}), - 0x000C: ('Contrast', - {0: 'Normal', - 1: 'Low', - 2: 'High'}), - 0x000D: ('Saturation', - {0: 'Normal', - 1: 'Low', - 2: 'High'}), - 0x0014: ('CCDSpeed', - {64: 'Normal', - 80: 'Normal', - 100: 'High', - 125: '+1.0', - 244: '+3.0', - 250: '+2.0'}), - } - -MAKERNOTE_FUJIFILM_TAGS={ - 0x0000: ('NoteVersion', make_string), - 0x1000: ('Quality', ), - 0x1001: ('Sharpness', - {1: 'Soft', - 2: 'Soft', - 3: 'Normal', - 4: 'Hard', - 5: 'Hard'}), - 0x1002: ('WhiteBalance', - {0: 'Auto', - 256: 'Daylight', - 512: 'Cloudy', - 768: 'DaylightColor-Fluorescent', - 769: 'DaywhiteColor-Fluorescent', - 770: 'White-Fluorescent', - 1024: 'Incandescent', - 3840: 'Custom'}), - 0x1003: ('Color', - {0: 'Normal', - 256: 'High', - 512: 'Low'}), - 0x1004: ('Tone', - {0: 'Normal', - 256: 'High', - 512: 'Low'}), - 0x1010: ('FlashMode', - {0: 'Auto', - 1: 'On', - 2: 'Off', - 3: 'Red Eye Reduction'}), - 0x1011: ('FlashStrength', ), - 0x1020: ('Macro', - {0: 'Off', - 1: 'On'}), - 0x1021: ('FocusMode', - {0: 'Auto', - 1: 'Manual'}), - 0x1030: ('SlowSync', - {0: 'Off', - 1: 'On'}), - 0x1031: ('PictureMode', - {0: 'Auto', - 1: 'Portrait', - 2: 'Landscape', - 4: 'Sports', - 5: 'Night', - 6: 'Program AE', - 256: 'Aperture Priority AE', - 512: 'Shutter Priority AE', - 768: 'Manual Exposure'}), - 0x1100: ('MotorOrBracket', - {0: 'Off', - 1: 'On'}), - 0x1300: ('BlurWarning', - {0: 'Off', - 1: 'On'}), - 0x1301: ('FocusWarning', - {0: 'Off', - 1: 'On'}), - 0x1302: ('AEWarning', - {0: 'Off', - 1: 'On'}), - } - -MAKERNOTE_CANON_TAGS = { - 0x0006: ('ImageType', ), - 0x0007: ('FirmwareVersion', ), - 0x0008: ('ImageNumber', ), - 0x0009: ('OwnerName', ), - } - -# this is in element offset, name, optional value dictionary format -MAKERNOTE_CANON_TAG_0x001 = { - 1: ('Macromode', - {1: 'Macro', - 2: 'Normal'}), - 2: ('SelfTimer', ), - 3: ('Quality', - {2: 'Normal', - 3: 'Fine', - 5: 'Superfine'}), - 4: ('FlashMode', - {0: 'Flash Not Fired', - 1: 'Auto', - 2: 'On', - 3: 'Red-Eye Reduction', - 4: 'Slow Synchro', - 5: 'Auto + Red-Eye Reduction', - 6: 'On + Red-Eye Reduction', - 16: 'external flash'}), - 5: ('ContinuousDriveMode', - {0: 'Single Or Timer', - 1: 'Continuous'}), - 7: ('FocusMode', - {0: 'One-Shot', - 1: 'AI Servo', - 2: 'AI Focus', - 3: 'MF', - 4: 'Single', - 5: 'Continuous', - 6: 'MF'}), - 10: ('ImageSize', - {0: 'Large', - 1: 'Medium', - 2: 'Small'}), - 11: ('EasyShootingMode', - {0: 'Full Auto', - 1: 'Manual', - 2: 'Landscape', - 3: 'Fast Shutter', - 4: 'Slow Shutter', - 5: 'Night', - 6: 'B&W', - 7: 'Sepia', - 8: 'Portrait', - 9: 'Sports', - 10: 'Macro/Close-Up', - 11: 'Pan Focus'}), - 12: ('DigitalZoom', - {0: 'None', - 1: '2x', - 2: '4x'}), - 13: ('Contrast', - {0xFFFF: 'Low', - 0: 'Normal', - 1: 'High'}), - 14: ('Saturation', - {0xFFFF: 'Low', - 0: 'Normal', - 1: 'High'}), - 15: ('Sharpness', - {0xFFFF: 'Low', - 0: 'Normal', - 1: 'High'}), - 16: ('ISO', - {0: 'See ISOSpeedRatings Tag', - 15: 'Auto', - 16: '50', - 17: '100', - 18: '200', - 19: '400'}), - 17: ('MeteringMode', - {3: 'Evaluative', - 4: 'Partial', - 5: 'Center-weighted'}), - 18: ('FocusType', - {0: 'Manual', - 1: 'Auto', - 3: 'Close-Up (Macro)', - 8: 'Locked (Pan Mode)'}), - 19: ('AFPointSelected', - {0x3000: 'None (MF)', - 0x3001: 'Auto-Selected', - 0x3002: 'Right', - 0x3003: 'Center', - 0x3004: 'Left'}), - 20: ('ExposureMode', - {0: 'Easy Shooting', - 1: 'Program', - 2: 'Tv-priority', - 3: 'Av-priority', - 4: 'Manual', - 5: 'A-DEP'}), - 23: ('LongFocalLengthOfLensInFocalUnits', ), - 24: ('ShortFocalLengthOfLensInFocalUnits', ), - 25: ('FocalUnitsPerMM', ), - 28: ('FlashActivity', - {0: 'Did Not Fire', - 1: 'Fired'}), - 29: ('FlashDetails', - {14: 'External E-TTL', - 13: 'Internal Flash', - 11: 'FP Sync Used', - 7: '2nd("Rear")-Curtain Sync Used', - 4: 'FP Sync Enabled'}), - 32: ('FocusMode', - {0: 'Single', - 1: 'Continuous'}), - } - -MAKERNOTE_CANON_TAG_0x004 = { - 7: ('WhiteBalance', - {0: 'Auto', - 1: 'Sunny', - 2: 'Cloudy', - 3: 'Tungsten', - 4: 'Fluorescent', - 5: 'Flash', - 6: 'Custom'}), - 9: ('SequenceNumber', ), - 14: ('AFPointUsed', ), - 15: ('FlashBias', - {0XFFC0: '-2 EV', - 0XFFCC: '-1.67 EV', - 0XFFD0: '-1.50 EV', - 0XFFD4: '-1.33 EV', - 0XFFE0: '-1 EV', - 0XFFEC: '-0.67 EV', - 0XFFF0: '-0.50 EV', - 0XFFF4: '-0.33 EV', - 0X0000: '0 EV', - 0X000C: '0.33 EV', - 0X0010: '0.50 EV', - 0X0014: '0.67 EV', - 0X0020: '1 EV', - 0X002C: '1.33 EV', - 0X0030: '1.50 EV', - 0X0034: '1.67 EV', - 0X0040: '2 EV'}), - 19: ('SubjectDistance', ), - } - -# extract multibyte integer in Motorola format (little endian) -def s2n_motorola(str): - x = 0 - for c in str: - x = (x << 8) | ord(c) - return x - -# extract multibyte integer in Intel format (big endian) -def s2n_intel(str): - x = 0 - y = 0L - for c in str: - x = x | (ord(c) << y) - y = y + 8 - return x - -# ratio object that eventually will be able to reduce itself to lowest -# common denominator for printing -def gcd(a, b): - if b == 0: - return a - else: - return gcd(b, a % b) - -class Ratio: - def __init__(self, num, den): - self.num = num - self.den = den - - def __repr__(self): - self.reduce() - if self.den == 1: - return str(self.num) - return '%d/%d' % (self.num, self.den) - - def reduce(self): - div = gcd(self.num, self.den) - if div > 1: - self.num = self.num / div - self.den = self.den / div - -# for ease of dealing with tags -class IFD_Tag: - def __init__(self, printable, tag, field_type, values, field_offset, - field_length): - # printable version of data - self.printable = printable - # tag ID number - self.tag = tag - # field type as index into FIELD_TYPES - self.field_type = field_type - # offset of start of field in bytes from beginning of IFD - self.field_offset = field_offset - # length of data field in bytes - self.field_length = field_length - # either a string or array of data items - self.values = values - - def __str__(self): - return self.printable - - def __repr__(self): - return '(0x%04X) %s=%s @ %d' % (self.tag, - FIELD_TYPES[self.field_type][2], - self.printable, - self.field_offset) - -# class that handles an EXIF header -class EXIF_header: - def __init__(self, file, endian, offset, fake_exif, debug=0): - self.file = file - self.endian = endian - self.offset = offset - self.fake_exif = fake_exif - self.debug = debug - self.tags = {} - - # convert slice to integer, based on sign and endian flags - # usually this offset is assumed to be relative to the beginning of the - # start of the EXIF information. For some cameras that use relative tags, - # this offset may be relative to some other starting point. - def s2n(self, offset, length, signed=0): - self.file.seek(self.offset+offset) - slice=self.file.read(length) - if self.endian == 'I': - val=s2n_intel(slice) - else: - val=s2n_motorola(slice) - # Sign extension ? - if signed: - msb=1L << (8*length-1) - if val & msb: - val=val-(msb << 1) - return val - - # convert offset to string - def n2s(self, offset, length): - s = '' - for dummy in range(length): - if self.endian == 'I': - s = s + chr(offset & 0xFF) - else: - s = chr(offset & 0xFF) + s - offset = offset >> 8 - return s - - # return first IFD - def first_IFD(self): - return self.s2n(4, 4) - - # return pointer to next IFD - def next_IFD(self, ifd): - entries=self.s2n(ifd, 2) - return self.s2n(ifd+2+12*entries, 4) - - # return list of IFDs in header - def list_IFDs(self): - i=self.first_IFD() - a=[] - while i: - a.append(i) - i=self.next_IFD(i) - return a - - # return list of entries in this IFD - def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0, stop_tag='UNDEF'): - entries=self.s2n(ifd, 2) - for i in range(entries): - # entry is index of start of this IFD in the file - entry = ifd + 2 + 12 * i - tag = self.s2n(entry, 2) - - # get tag name early to avoid errors, help debug - tag_entry = dict.get(tag) - if tag_entry: - tag_name = tag_entry[0] - else: - tag_name = 'Tag 0x%04X' % tag - - # ignore certain tags for faster processing - if not (not detailed and tag in IGNORE_TAGS): - field_type = self.s2n(entry + 2, 2) - if not 0 < field_type < len(FIELD_TYPES): - # unknown field type - raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag)) - typelen = FIELD_TYPES[field_type][0] - count = self.s2n(entry + 4, 4) - offset = entry + 8 - if count * typelen > 4: - # offset is not the value; it's a pointer to the value - # if relative we set things up so s2n will seek to the right - # place when it adds self.offset. Note that this 'relative' - # is for the Nikon type 3 makernote. Other cameras may use - # other relative offsets, which would have to be computed here - # slightly differently. - if relative: - tmp_offset = self.s2n(offset, 4) - offset = tmp_offset + ifd - self.offset + 4 - if self.fake_exif: - offset = offset + 18 - else: - offset = self.s2n(offset, 4) - field_offset = offset - if field_type == 2: - # special case: null-terminated ASCII string - if count != 0: - self.file.seek(self.offset + offset) - values = self.file.read(count) - values = values.strip().replace('\x00', '') - else: - values = '' - else: - values = [] - signed = (field_type in [6, 8, 9, 10]) - for dummy in range(count): - if field_type in (5, 10): - # a ratio - value = Ratio(self.s2n(offset, 4, signed), - self.s2n(offset + 4, 4, signed)) - else: - value = self.s2n(offset, typelen, signed) - values.append(value) - offset = offset + typelen - # now "values" is either a string or an array - if count == 1 and field_type != 2: - printable=str(values[0]) - else: - printable=str(values) - # compute printable version of values - if tag_entry: - if len(tag_entry) != 1: - # optional 2nd tag element is present - if callable(tag_entry[1]): - # call mapping function - printable = tag_entry[1](values) - else: - printable = '' - for i in values: - # use lookup table for this tag - printable += tag_entry[1].get(i, repr(i)) - - self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag, - field_type, - values, field_offset, - count * typelen) - if self.debug: - print ' debug: %s: %s' % (tag_name, - repr(self.tags[ifd_name + ' ' + tag_name])) - - if tag_name == stop_tag: - break - - # extract uncompressed TIFF thumbnail (like pulling teeth) - # we take advantage of the pre-existing layout in the thumbnail IFD as - # much as possible - def extract_TIFF_thumbnail(self, thumb_ifd): - entries = self.s2n(thumb_ifd, 2) - # this is header plus offset to IFD ... - if self.endian == 'M': - tiff = 'MM\x00*\x00\x00\x00\x08' - else: - tiff = 'II*\x00\x08\x00\x00\x00' - # ... plus thumbnail IFD data plus a null "next IFD" pointer - self.file.seek(self.offset+thumb_ifd) - tiff += self.file.read(entries*12+2)+'\x00\x00\x00\x00' - - # fix up large value offset pointers into data area - for i in range(entries): - entry = thumb_ifd + 2 + 12 * i - tag = self.s2n(entry, 2) - field_type = self.s2n(entry+2, 2) - typelen = FIELD_TYPES[field_type][0] - count = self.s2n(entry+4, 4) - oldoff = self.s2n(entry+8, 4) - # start of the 4-byte pointer area in entry - ptr = i * 12 + 18 - # remember strip offsets location - if tag == 0x0111: - strip_off = ptr - strip_len = count * typelen - # is it in the data area? - if count * typelen > 4: - # update offset pointer (nasty "strings are immutable" crap) - # should be able to say "tiff[ptr:ptr+4]=newoff" - newoff = len(tiff) - tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:] - # remember strip offsets location - if tag == 0x0111: - strip_off = newoff - strip_len = 4 - # get original data and store it - self.file.seek(self.offset + oldoff) - tiff += self.file.read(count * typelen) - - # add pixel strips and update strip offset info - old_offsets = self.tags['Thumbnail StripOffsets'].values - old_counts = self.tags['Thumbnail StripByteCounts'].values - for i in range(len(old_offsets)): - # update offset pointer (more nasty "strings are immutable" crap) - offset = self.n2s(len(tiff), strip_len) - tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:] - strip_off += strip_len - # add pixel strip to end - self.file.seek(self.offset + old_offsets[i]) - tiff += self.file.read(old_counts[i]) - - self.tags['TIFFThumbnail'] = tiff - - # decode all the camera-specific MakerNote formats - - # Note is the data that comprises this MakerNote. The MakerNote will - # likely have pointers in it that point to other parts of the file. We'll - # use self.offset as the starting point for most of those pointers, since - # they are relative to the beginning of the file. - # - # If the MakerNote is in a newer format, it may use relative addressing - # within the MakerNote. In that case we'll use relative addresses for the - # pointers. - # - # As an aside: it's not just to be annoying that the manufacturers use - # relative offsets. It's so that if the makernote has to be moved by the - # picture software all of the offsets don't have to be adjusted. Overall, - # this is probably the right strategy for makernotes, though the spec is - # ambiguous. (The spec does not appear to imagine that makernotes would - # follow EXIF format internally. Once they did, it's ambiguous whether - # the offsets should be from the header at the start of all the EXIF info, - # or from the header at the start of the makernote.) - def decode_maker_note(self): - note = self.tags['EXIF MakerNote'] - make = self.tags['Image Make'].printable - # model = self.tags['Image Model'].printable # unused - - # Nikon - # The maker note usually starts with the word Nikon, followed by the - # type of the makernote (1 or 2, as a short). If the word Nikon is - # not at the start of the makernote, it's probably type 2, since some - # cameras work that way. - if make in ('NIKON', 'NIKON CORPORATION'): - if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]: - if self.debug: - print "Looks like a type 1 Nikon MakerNote." - self.dump_IFD(note.field_offset+8, 'MakerNote', - dict=MAKERNOTE_NIKON_OLDER_TAGS) - elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]: - if self.debug: - print "Looks like a labeled type 2 Nikon MakerNote" - if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]: - raise ValueError("Missing marker tag '42' in MakerNote.") - # skip the Makernote label and the TIFF header - self.dump_IFD(note.field_offset+10+8, 'MakerNote', - dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1) - else: - # E99x or D1 - if self.debug: - print "Looks like an unlabeled type 2 Nikon MakerNote" - self.dump_IFD(note.field_offset, 'MakerNote', - dict=MAKERNOTE_NIKON_NEWER_TAGS) - return - - # Olympus - if make.startswith('OLYMPUS'): - self.dump_IFD(note.field_offset+8, 'MakerNote', - dict=MAKERNOTE_OLYMPUS_TAGS) - # TODO - #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),): - # self.decode_olympus_tag(self.tags[i[0]].values, i[1]) - #return - - # Casio - if make == 'Casio': - self.dump_IFD(note.field_offset, 'MakerNote', - dict=MAKERNOTE_CASIO_TAGS) - return - - # Fujifilm - if make == 'FUJIFILM': - # bug: everything else is "Motorola" endian, but the MakerNote - # is "Intel" endian - endian = self.endian - self.endian = 'I' - # bug: IFD offsets are from beginning of MakerNote, not - # beginning of file header - offset = self.offset - self.offset += note.field_offset - # process note with bogus values (note is actually at offset 12) - self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS) - # reset to correct values - self.endian = endian - self.offset = offset - return - - # Canon - if make == 'Canon': - self.dump_IFD(note.field_offset, 'MakerNote', - dict=MAKERNOTE_CANON_TAGS) - for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001), - ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)): - self.canon_decode_tag(self.tags[i[0]].values, i[1]) - return - - # decode Olympus MakerNote tag based on offset within tag - def olympus_decode_tag(self, value, dict): - pass - - # decode Canon MakerNote tag based on offset within tag - # see http://www.burren.cx/david/canon.html by David Burren - def canon_decode_tag(self, value, dict): - for i in range(1, len(value)): - x=dict.get(i, ('Unknown', )) - if self.debug: - print i, x - name=x[0] - if len(x) > 1: - val=x[1].get(value[i], 'Unknown') - else: - val=value[i] - # it's not a real IFD Tag but we fake one to make everybody - # happy. this will have a "proprietary" type - self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None, - None, None) - -# process an image file (expects an open file object) -# this is the function that has to deal with all the arbitrary nasty bits -# of the EXIF standard -def process_file(f, stop_tag='UNDEF', details=True, debug=False): - # yah it's cheesy... - global detailed - detailed = details - - # by default do not fake an EXIF beginning - fake_exif = 0 - - # determine whether it's a JPEG or TIFF - data = f.read(12) - if data[0:4] in ['II*\x00', 'MM\x00*']: - # it's a TIFF file - f.seek(0) - endian = f.read(1) - f.read(1) - offset = 0 - elif data[0:2] == '\xFF\xD8': - # it's a JPEG file - while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'): - length = ord(data[4])*256+ord(data[5]) - f.read(length-8) - # fake an EXIF beginning of file - data = '\xFF\x00'+f.read(10) - fake_exif = 1 - if data[2] == '\xFF' and data[6:10] == 'Exif': - # detected EXIF header - offset = f.tell() - endian = f.read(1) - else: - # no EXIF information - return {} - else: - # file format not recognized - return {} - - # deal with the EXIF info we found - if debug: - print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format' - hdr = EXIF_header(f, endian, offset, fake_exif, debug) - ifd_list = hdr.list_IFDs() - ctr = 0 - for i in ifd_list: - if ctr == 0: - IFD_name = 'Image' - elif ctr == 1: - IFD_name = 'Thumbnail' - thumb_ifd = i - else: - IFD_name = 'IFD %d' % ctr - if debug: - print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i) - hdr.dump_IFD(i, IFD_name, stop_tag=stop_tag) - # EXIF IFD - exif_off = hdr.tags.get(IFD_name+' ExifOffset') - if exif_off: - if debug: - print ' EXIF SubIFD at offset %d:' % exif_off.values[0] - hdr.dump_IFD(exif_off.values[0], 'EXIF', stop_tag=stop_tag) - # Interoperability IFD contained in EXIF IFD - intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset') - if intr_off: - if debug: - print ' EXIF Interoperability SubSubIFD at offset %d:' \ - % intr_off.values[0] - hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability', - dict=INTR_TAGS, stop_tag=stop_tag) - # GPS IFD - gps_off = hdr.tags.get(IFD_name+' GPSInfo') - if gps_off: - if debug: - print ' GPS SubIFD at offset %d:' % gps_off.values[0] - hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS, stop_tag=stop_tag) - ctr += 1 - - # extract uncompressed TIFF thumbnail - thumb = hdr.tags.get('Thumbnail Compression') - if thumb and thumb.printable == 'Uncompressed TIFF': - hdr.extract_TIFF_thumbnail(thumb_ifd) - - # JPEG thumbnail (thankfully the JPEG data is stored as a unit) - thumb_off = hdr.tags.get('Thumbnail JPEGInterchangeFormat') - if thumb_off: - f.seek(offset+thumb_off.values[0]) - size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0] - hdr.tags['JPEGThumbnail'] = f.read(size) - - # deal with MakerNote contained in EXIF IFD - if 'EXIF MakerNote' in hdr.tags and detailed: - hdr.decode_maker_note() - - # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote - # since it's not allowed in a uncompressed TIFF IFD - if 'JPEGThumbnail' not in hdr.tags: - thumb_off=hdr.tags.get('MakerNote JPEGThumbnail') - if thumb_off: - f.seek(offset+thumb_off.values[0]) - hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length) - - return hdr.tags - - -# show command line usage -def usage(exit_status): - msg = 'Usage: EXIF.py [OPTIONS] file1 [file2 ...]\n' - msg += 'Extract EXIF information from digital camera image files.\n\nOptions:\n' - msg += '-q --quick Do not process MakerNotes.\n' - msg += '-t TAG --stop-tag TAG Stop processing when this tag is retrieved.\n' - msg += '-d --debug Run in debug mode.\n' - print msg - sys.exit(exit_status) - -# library test/debug function (dump given files) -if __name__ == '__main__': - import sys - import getopt - - # parse command line options/arguments - try: - opts, args = getopt.getopt(sys.argv[1:], "hqdt:v", ["help", "quick", "debug", "stop-tag="]) - except getopt.GetoptError: - usage(2) - if args == []: - usage(2) - detailed = True - stop_tag = 'UNDEF' - debug = False - for o, a in opts: - if o in ("-h", "--help"): - usage(0) - if o in ("-q", "--quick"): - detailed = False - if o in ("-t", "--stop-tag"): - stop_tag = a - if o in ("-d", "--debug"): - debug = True - - # output info for each file - for filename in args: - try: - file=open(filename, 'rb') - except: - print "'%s' is unreadable\n"%filename - continue - print filename + ':' - # get the tags - data = process_file(file, stop_tag=stop_tag, details=detailed, debug=debug) - if not data: - print 'No EXIF information found' - continue - - x=data.keys() - x.sort() - for i in x: - if i in ('JPEGThumbnail', 'TIFFThumbnail'): - continue - try: - print ' %s (%s): %s' % \ - (i, FIELD_TYPES[data[i].field_type][2], data[i].printable) - except: - print 'error', i, '"', data[i], '"' - if 'JPEGThumbnail' in data: - print 'File has JPEG thumbnail' - print -
--- a/mysite/photologue/utils/reflection.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -""" Function for generating web 2.0 style image reflection effects. - -Copyright (c) 2007, Justin C. Driscoll -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of reflection.py nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -""" - -try: - import Image - import ImageColor -except ImportError: - try: - from PIL import Image - from PIL import ImageColor - except ImportError: - raise ImportError("The Python Imaging Library was not found.") - - -def add_reflection(im, bgcolor="#00000", amount=0.4, opacity=0.6): - """ Returns the supplied PIL Image (im) with a reflection effect - - bgcolor The background color of the reflection gradient - amount The height of the reflection as a percentage of the orignal image - opacity The initial opacity of the reflection gradient - - Originally written for the Photologue image management system for Django - and Based on the original concept by Bernd Schlapsi - - """ - # convert bgcolor string to rgb value - background_color = ImageColor.getrgb(bgcolor) - - # copy orignial image and flip the orientation - reflection = im.copy().transpose(Image.FLIP_TOP_BOTTOM) - - # create a new image filled with the bgcolor the same size - background = Image.new("RGB", im.size, background_color) - - # calculate our alpha mask - start = int(255 - (255 * opacity)) # The start of our gradient - steps = int(255 * amount) # the number of intermedite values - increment = (255 - start) / float(steps) - mask = Image.new('L', (1, 255)) - for y in range(255): - if y < steps: - val = int(y * increment + start) - else: - val = 255 - mask.putpixel((0, y), val) - alpha_mask = mask.resize(im.size) - - # merge the reflection onto our background color using the alpha mask - reflection = Image.composite(background, reflection, alpha_mask) - - # crop the reflection - reflection_height = int(im.size[1] * amount) - reflection = reflection.crop((0, 0, im.size[0], reflection_height)) - - # create new image sized to hold both the original image and the reflection - composite = Image.new("RGB", (im.size[0], im.size[1]+reflection_height), background_color) - - # paste the orignal image and the reflection into the composite image - composite.paste(im, (0, 0)) - composite.paste(reflection, (0, im.size[1])) - - # return the image complete with reflection effect - return composite - \ No newline at end of file
--- a/mysite/photologue/utils/watermark.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -""" Function for applying watermarks to images. - -Original found here: -http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/362879 - -""" - -try: - import Image - import ImageEnhance -except ImportError: - try: - from PIL import Image - from PIL import ImageEnhance - except ImportError: - raise ImportError("The Python Imaging Library was not found.") - -def reduce_opacity(im, opacity): - """Returns an image with reduced opacity.""" - assert opacity >= 0 and opacity <= 1 - if im.mode != 'RGBA': - im = im.convert('RGBA') - else: - im = im.copy() - alpha = im.split()[3] - alpha = ImageEnhance.Brightness(alpha).enhance(opacity) - im.putalpha(alpha) - return im - -def apply_watermark(im, mark, position, opacity=1): - """Adds a watermark to an image.""" - if opacity < 1: - mark = reduce_opacity(mark, opacity) - if im.mode != 'RGBA': - im = im.convert('RGBA') - # create a transparent layer the size of the image and draw the - # watermark in that layer. - layer = Image.new('RGBA', im.size, (0,0,0,0)) - if position == 'tile': - for y in range(0, im.size[1], mark.size[1]): - for x in range(0, im.size[0], mark.size[0]): - layer.paste(mark, (x, y)) - elif position == 'scale': - # scale, but preserve the aspect ratio - ratio = min( - float(im.size[0]) / mark.size[0], float(im.size[1]) / mark.size[1]) - w = int(mark.size[0] * ratio) - h = int(mark.size[1] * ratio) - mark = mark.resize((w, h)) - layer.paste(mark, ((im.size[0] - w) / 2, (im.size[1] - h) / 2)) - else: - layer.paste(mark, position) - # composite the watermark with the layer - return Image.composite(layer, im, layer) - -def test(): - im = Image.open('test.png') - mark = Image.open('overlay.png') - watermark(im, mark, 'tile', 0.5).show() - watermark(im, mark, 'scale', 1.0).show() - watermark(im, mark, (100, 100), 0.5).show() - -if __name__ == '__main__': - test()
--- a/mysite/pl-admin.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -import getopt, sys - -try: - import settings # Assumed to be in the same directory. - from django.core.management import setup_environ -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) - sys.exit(1) - - -def precache(sizes=[], reset=False): - # setup django environment - setup_environ(settings) - - # import models - from photologue.models import Photo, PhotoSize, PhotoSizeCache - - cache = PhotoSizeCache() - - print 'Caching photos, this may take a while...' - - for photo in Photo.objects.all(): - if len(sizes): - for size in sizes: - photosize = cache.sizes.get(size, None) - if photosize is None: - print '\nA photosize named "%s" was not found...' % size - else: - if reset: - photo.remove_size(photosize) - photo.create_size(photosize) - else: - for size in caches.sizes.values(): - if reset: - Photo.remove_size(photosize) - photo.create_size(photosize) - - print ' Complete.' - sys.exit(2) - - -def reset(): - # setup django environment - setup_environ(settings) - - # import models - from photologue.models import Photo, PhotoSize - - print 'Reseting photo cache, this may take a while...' - - for photo in Photo.objects.all(): - photo.clear_cache() - - print ' Complete.' - sys.exit(2) - - -def usage(): - print """ - -pl-admin.py - Photologue administration script. - -Available Commands: - pl-admin.py create Resizes and caches all defined photo sizes for each image. - pl-admin.py reset Removes all cached images. - -Options: - --reset (-r) If calling create the script will clear the existing photo cache - before regenerating the specified size (or sizes) - --size (-s) The name of a photosize generate - -Usage: - pl-admin.py [options] command - -Examples: - pl-admin.py -r -s=thumbnail create - pl-admin.py -s=thumbnail -s=display create - pl-admin.py reset - -""" - -def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], "hrs:", - ["help", "reset", "sizes="]) - except getopt.GetoptError, err: - print str(err) - usage() - sys.exit(2) - r = False - s = [] - for o, a in opts: - if o in ("-h", "--help"): - usage() - sys.exit(2) - if o in ("-r", "--reset"): - r = True - elif o in ("-s", "--sizes"): - s.append(a.strip('=')) - else: - usage() - sys.exit(2) - - if len(args) == 1: - command = args[0] - if command == 'create': - precache(s, r) - elif command == 'reset': - reset() - - usage() - - -if __name__ == '__main__': - main()
--- a/mysite/settings/base.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -# Base Django settings for madeira project. - -import os -import django.utils.simplejson as json - -PROJECT_PATH = os.path.abspath(os.path.join(os.path.split(__file__)[0], '..')) - -DEBUG = True -TEMPLATE_DEBUG = DEBUG - -ADMINS = [ - ('Brian Neal', 'admin@surfguitar101.com'), -] - -MANAGERS = ADMINS - -INTERNAL_IPS = ['127.0.0.1'] - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = 'America/Chicago' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = False - -# Absolute path to the directory that holds media. -# Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = os.path.abspath(os.path.join(PROJECT_PATH, '..', 'media')) - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash if there is a path component (optional in other cases). -# Examples: "http://media.lawrence.com", "http://example.com/media/" -MEDIA_URL = '/media/' - -# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a -# trailing slash. -# Examples: "http://foo.com/media/", "/media/". -ADMIN_MEDIA_PREFIX = '/static/admin/' - -# Staticfiles settings: -STATICFILES_DIRS = [ - os.path.abspath(os.path.join(PROJECT_PATH, '..', 'static')), -] -STATIC_ROOT = '/tmp/test_madeira_static_root' -STATIC_URL = '/static/' - -# Make this unique, and don't share it with anybody. -SECRETS = json.load(open(os.path.join(PROJECT_PATH, 'settings', 'secrets.json'))) -SECRET_KEY = SECRETS['SECRET_KEY'] - -TEMPLATE_LOADERS = [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', -] - -MIDDLEWARE_CLASSES = [ - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.middleware.doc.XViewMiddleware', - 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', -] - -ROOT_URLCONF = 'mysite.urls' - -# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". -# Always use forward slashes, even on Windows. -# Don't forget to use absolute paths, not relative paths. -TEMPLATE_DIRS = [ - os.path.join(PROJECT_PATH, 'templates'), - os.path.join(PROJECT_PATH, 'templates', 'band'), - os.path.join(PROJECT_PATH, 'photologue', 'templates'), -] - -TEMPLATE_CONTEXT_PROCESSORS = [ - "django.contrib.auth.context_processors.auth", - "django.core.context_processors.debug", - "django.core.context_processors.request", - "django.core.context_processors.media", - "django.core.context_processors.static", - "django.contrib.messages.context_processors.messages", -] - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.admindocs', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.flatpages', - 'django.contrib.markup', - 'django.contrib.messages', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.staticfiles', - 'band', - 'photologue', -] - -####################################################################### -# Messages -####################################################################### -MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' - -####################################################################### -# Email -####################################################################### -EMAIL_HOST = 'localhost' -EMAIL_PORT = 1025 - -####################################################################### -# Sessions -####################################################################### -SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" -SESSION_COOKIE_AGE = 2 * 7 * 24 * 60 * 60 # 2 weeks in seconds -SESSION_COOKIE_DOMAIN = None -SESSION_COOKIE_NAME = 'madeira_sessionid' -SESSION_COOKIE_PATH = '/' -SESSION_COOKIE_SECURE = False -SESSION_EXPIRE_AT_BROWSER_CLOSE = False -SESSION_SAVE_EVERY_REQUEST = False
--- a/mysite/settings/local.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -""" -Local Django settings for The Madeira site. -The contents of this file will vary depending on the local installation. - -""" -from settings.base import * - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': SECRETS['DB_NAME'], - 'USER': SECRETS['DB_USER'], - 'PASSWORD': SECRETS['DB_PASSWORD'], - }, -} - -# Django Debug Toolbar support -if DEBUG: - try: - import debug_toolbar - except ImportError: - pass - else: - i = MIDDLEWARE_CLASSES.index('django.middleware.common.CommonMiddleware') - MIDDLEWARE_CLASSES.insert(i + 1, - 'debug_toolbar.middleware.DebugToolbarMiddleware') - INSTALLED_APPS.append('debug_toolbar') - DEBUG_TOOLBAR_CONFIG = { - 'INTERCEPT_REDIRECTS': True, - } - -# Logging configuration - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': True, - 'formatters': { - 'verbose': { - 'format': '%(asctime)s %(levelname)s %(module)s %(process)d %(thread)d %(message)s' - }, - 'simple': { - 'format': '%(asctime)s %(levelname)s %(message)s' - }, - }, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'level': 'DEBUG', - 'formatter': 'simple', - }, - 'file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'level': 'DEBUG', - 'formatter': 'simple', - 'filename': os.path.join(PROJECT_PATH, 'logs', 'madeira.log'), - 'mode': 'a', - 'maxBytes': 100 * 1024, - 'backupCount': 10, - }, - 'mail_admins': { - 'class': 'django.utils.log.AdminEmailHandler', - 'level': 'ERROR', - 'formatter': 'simple', - }, - }, - 'loggers': { - 'django':{ - 'level': 'WARNING', - 'propagate': False, - 'handlers': ['file'], - }, - }, - 'root': { - 'level': 'DEBUG', - 'handlers': ['file'], - }, -}
--- a/mysite/settings/production.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ -# Django production settings for the madeira project. - -from settings.base import * - -DEBUG = False -TEMPLATE_DEBUG = DEBUG - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'madeira_django', - 'USER': SECRETS['DB_USER'], - 'PASSWORD': SECRETS['DB_PASSWORD'], - }, -} - -STATIC_ROOT = os.path.abspath(os.path.join(PROJECT_PATH, '..', 'static_serve')) - -TEMPLATE_LOADERS = [ - ('django.template.loaders.cached.Loader', ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - )), -] - -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': '127.0.0.1:11211', - 'TIMEOUT': 600, - }, -} -CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True -CACHE_MIDDLEWARE_SECONDS = 600 -CACHE_MIDDLEWARE_KEY_PREFIX = '' - -MIDDLEWARE_CLASSES.insert(0, 'django.middleware.cache.UpdateCacheMiddleware') -MIDDLEWARE_CLASSES.append('django.middleware.cache.FetchFromCacheMiddleware') - -EMAIL_HOST = 'localhost' -EMAIL_PORT = 25 - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': True, - 'formatters': { - 'verbose': { - 'format': '%(asctime)s %(levelname)s %(module)s %(process)d %(thread)d %(message)s' - }, - 'simple': { - 'format': '%(asctime)s %(levelname)s %(message)s' - }, - }, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'level': 'DEBUG', - 'formatter': 'simple', - }, - 'file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'level': 'DEBUG', - 'formatter': 'simple', - 'filename': os.path.join(PROJECT_PATH, 'logs', 'madeira.log'), - 'mode': 'a', - 'maxBytes': 100 * 1024, - 'backupCount': 10, - }, - 'mail_admins': { - 'class': 'django.utils.log.AdminEmailHandler', - 'level': 'ERROR', - 'formatter': 'simple', - }, - }, - 'loggers': { - 'django':{ - 'level': 'WARNING', - 'propagate': False, - 'handlers': ['file'], - }, - 'django.request':{ - 'level': 'ERROR', - 'propagate': True, - 'handlers': ['mail_admins'], - }, - }, - 'root': { - 'level': 'INFO', - 'handlers': ['file'], - }, -}
--- a/mysite/templates/404.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" -"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head><title>Page Not Found</title> -<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/theme.css" /> -<link rel="shortcut icon" type="image/vnd.microsoft.com" href="{{ STATIC_URL }}images/favicon.ico" /> -</head> -<body> - - <h1>Not Found</h1> - - <p>The requested URL {{ request.path|escape }} was not found on this server.</p> - -</body> -</html>
--- a/mysite/templates/500.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" -"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head><title>Internal Server Error</title> -</head> -<body> - - <h1>Internal Server Error</h1> - - <p>We're sorry, that page is currently unavailable due to a server misconfiguration.</p> - <p>The server administrator has been notified, and we apologise for any inconvenience.</p> - -</body> -</html>
--- a/mysite/templates/admin/band/email.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -{% extends 'admin/base_site.html' %} -{% load adminmedia %} -{% block title %}The Madeira | Mailing List Email Form{% endblock %} -{% block extrastyle %} -<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" /> -{% endblock %} -{% block bodyclass %}change-form{% endblock %} -{% block breadcrumbs %} -<div class="breadcrumbs"> - <a href="/admin">Home</a> -</div> -{% endblock %} -{% block content %} -<h1>Madeira Mailing List Email Form</h1> -<div id="content-main"> -<p>Use this form to send an email to all subscribers of the The Madeira mailing list.</p> -<form method="post" action="{{ request.build_absolute_uri }}">{% csrf_token %} -<div> -<fieldset class="module aligned"> - {% for field in form %} - <div class="form-row {% if field.field.required %}required{% endif %} {% if field.errors %}errors{% endif %}"> - {% if field.errors %}{{ field.errors }}{% endif %} - {{ field.label_tag }} - {{ field }} - </div> - {% endfor %} -</fieldset> -<div class="submit-row"> - <input type="submit" name="Send" value="Send" class="default" /> -</div> -</div> -</form> -</div> -{% endblock %}
--- a/mysite/templates/admin/band/email_sent.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -{% extends 'admin/base_site.html' %} -{% load adminmedia %} -{% block title %}The Madeira | Mailing List Email Sent{% endblock %} -{% block extrastyle %} -<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" /> -{% endblock %} -{% block breadcrumbs %} -<div class="breadcrumbs"> - <a href="/admin">Home</a> -</div> -{% endblock %} -{% block content %} -<h1>Madeira Mailing List Email Sent</h1> -<div id="content-main"> - <p>Your email has been sent.</p> -</div> -{% endblock %}
--- a/mysite/templates/admin/base_site.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -{% extends "admin/base.html" %} -{% load i18n %} - -{% block title %}{{ title }} | {% trans 'Madeira site admin' %}{% endblock %} -{% block extrahead %} -<link rel="shortcut icon" href="{{ STATIC_URL }}images/favicon.ico" type="image/vnd.microsoft.icon" /> -{% endblock %} - -{% block branding %} -<h1 id="site-name">{% trans 'Madeira site administration' %}</h1> -{% endblock %} - -{% block nav-global %}{% endblock %}
--- a/mysite/templates/admin/index.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load i18n %} - -{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css" />{% endblock %} - -{% block coltype %}colMS{% endblock %} -{% block bodyclass %}dashboard{% endblock %} -{% block breadcrumbs %}{% endblock %} -{% block content %} -<div id="content-main"> - -{% if app_list %} - <div class="module"> - <table summary="Madeira custom views"> - <caption>Madeira Quick Links</caption> - <!-- ============================================================================== --> - {% if perms.band.add_news or perms.band.change_news %} - <tr> - <th>{% if perms.band.change_news %}<a href="band/news/">{% endif %}News Items{% if perms.band.change_news %}</a>{% endif %}</th> - <td class="x50">{% if perms.band.add_news %}<a href="band/news/add/" class="addlink">{% endif %}Add{% if perms.band.add_news %}</a>{% endif %}</td> - <td class="x75">{% if perms.band.change_news %}<a href="band/news/" class="changelink">{% endif %}Change{% if perms.band.change_news %}</a>{% endif %}</td> - </tr> - {% endif %} - {% if perms.band.add_member or perms.band.change_member %} - <tr> - <th>{% if perms.band.change_member %}<a href="band/member/">{% endif %}Biography & Gear{% if perms.band.change_member %}</a>{% endif %}</th> - <td class="x50">{% if perms.band.add_member %}<a href="band/member/add/" class="addlink">{% endif %}Add{% if perms.band.add_member %}</a>{% endif %}</td> - <td class="x75">{% if perms.band.change_member %}<a href="band/member/" class="changelink">{% endif %}Change{% if perms.band.change_member %}</a>{% endif %}</td> - </tr> - {% endif %} - {% if perms.band.add_gig or perms.band.change_gig %} - <tr> - <th>{% if perms.band.change_gig %}<a href="band/gig/">{% endif %}Gigs{% if perms.band.change_gig %}</a>{% endif %}</th> - <td class="x50">{% if perms.band.add_gig %}<a href="band/gig/add/" class="addlink">{% endif %}Add{% if perms.band.add_gig %}</a>{% endif %}</td> - <td class="x75">{% if perms.band.change_gig %}<a href="band/gig/" class="changelink">{% endif %}Change{% if perms.band.change_gig %}</a>{% endif %}</td> - </tr> - {% endif %} - {% if perms.band.add_article or perms.band.change_article %} - <tr> - <th>{% if perms.band.change_article %}<a href="band/article/">{% endif %}Press Items{% if perms.band.change_article %}</a>{% endif %}</th> - <td class="x50">{% if perms.band.add_article %}<a href="band/article/add/" class="addlink">{% endif %}Add{% if perms.band.add_article %}</a>{% endif %}</td> - <td class="x75">{% if perms.band.change_article %}<a href="band/article/" class="changelink">{% endif %}Change{% if perms.band.change_article %}</a>{% endif %}</td> - </tr> - {% endif %} - {% if perms.band.add_mp3_set or perms.band.change_mp3_set %} - <tr> - <th>{% if perms.band.change_mp3_set %}<a href="band/mp3_set/">{% endif %}Songs{% if perms.band.change_mp3_set %}</a>{% endif %}</th> - <td class="x50">{% if perms.band.add_mp3_set %}<a href="band/mp3_set/add/" class="addlink">{% endif %}Add{% if perms.band.add_mp3_set %}</a>{% endif %}</td> - <td class="x75">{% if perms.band.change_mp3_set %}<a href="band/mp3_set/" class="changelink">{% endif %}Change{% if perms.band.change_mp3_set %}</a>{% endif %}</td> - </tr> - {% endif %} - {% if perms.band.add_video_set or perms.band.change_video_set %} - <tr> - <th>{% if perms.band.change_video_set %}<a href="band/video_set/">{% endif %}Videos{% if perms.band.change_video_set %}</a>{% endif %}</th> - <td class="x50">{% if perms.band.add_video_set %}<a href="band/video_set/add/" class="addlink">{% endif %}Add{% if perms.band.add_video_set %}</a>{% endif %}</td> - <td class="x75">{% if perms.band.change_video_set %}<a href="band/video_set/" class="changelink">{% endif %}Change{% if perms.band.change_video_set %}</a>{% endif %}</td> - </tr> - {% endif %} - {% if perms.band.add_merchandise or perms.band.change_merchandise %} - <tr> - <th>{% if perms.band.change_merchandise %}<a href="band/merchandise/">{% endif %}Merchandise{% if perms.band.change_merchandise %}</a>{% endif %}</th> - <td class="x50">{% if perms.band.add_merchandise %}<a href="band/merchandise/add/" class="addlink">{% endif %}Add{% if perms.band.add_merchandise %}</a>{% endif %}</td> - <td class="x75">{% if perms.band.change_merchandise %}<a href="band/merchandise/" class="changelink">{% endif %}Change{% if perms.band.change_merchandise %}</a>{% endif %}</td> - </tr> - {% endif %} - - - <!-- ============================================================================== --> - <tr><th scope="row"><a href="{% url band.admin_views.email %}">Send Email to Mailing List</a></th> - <td> </td> - <td> </td> - </tr> - </table> - </div> - - - {% for app in app_list %} - <div class="module"> - <table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}"> - <caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption> - {% for model in app.models %} - <tr> - {% if model.perms.change %} - <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th> - {% else %} - <th scope="row">{{ model.name }}</th> - {% endif %} - - {% if model.perms.add %} - <td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td> - {% else %} - <td> </td> - {% endif %} - - {% if model.perms.change %} - <td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td> - {% else %} - <td> </td> - {% endif %} - </tr> - {% endfor %} - </table> - </div> - {% endfor %} -{% else %} - <p>{% trans "You don't have permission to edit anything." %}</p> -{% endif %} -</div> -{% endblock %} - -{% block sidebar %} -<div id="content-related"> - <div class="module" id="recent-actions-module"> - <h2>{% trans 'Recent Actions' %}</h2> - <h3>{% trans 'My Actions' %}</h3> - {% load log %} - {% get_admin_log 10 as admin_log for_user user %} - {% if not admin_log %} - <p>{% trans 'None available' %}</p> - {% else %} - <ul class="actionlist"> - {% for entry in admin_log %} - <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span></li> - {% endfor %} - </ul> - {% endif %} - </div> -</div> -{% endblock %}
--- a/mysite/templates/band/base.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" -"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -{% load url from future %} -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head><title>{% block title %}{% endblock %}</title> -<meta http-equiv="Content-Type" content="text/html" /> -<meta http-equiv="Content-Language" content="en-US" /> -<meta name="robots" content="all" /> -<meta name="Author" content="Brian Neal" /> -<meta name="copyright" content="© 2007-2010 Brian Neal" /> -<meta name="keywords" lang="en-us" content="instrumental surf, surf, guitar, musician, instro, surf music, Dick Dale, Atlantics, Surf Coasters, Fender, Strat, Stratocaster, Destination Earth, Destination: Earth!,Space Cossacks, Troubadours, reverb" /> -<meta name="description" lang="en-us" content="Home page for the instrumental surf band The Madeira. The Madeira combine high energy performances reminiscent of The Atlantics and Dick Dale with exotic melodies and an unusually high level of musicianship. This page contains show dates, photos, videos, and news about the band." /> -<link rel="stylesheet" href="{{ STATIC_URL }}css/blueprint/screen.css" type="text/css" media="screen, projection" /> -<link rel="stylesheet" href="{{ STATIC_URL }}css/blueprint/print.css" type="text/css" media="print" /> -<!--[if lt IE 8]> -<link rel="stylesheet" href="{{ STATIC_URL }}css/blueprint/ie.css" type="text/css" media="screen, projection" /> -<![endif]--> -<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/theme.css" /> -{% block custom_css %}{% endblock %} -{% block custom_js %}{% endblock %} -<link rel="shortcut icon" type="image/vnd.microsoft.com" href="{{ STATIC_URL }}images/favicon.ico" /> -</head> -<body> -<div class="container"> - -<div id="header" class="span-24 last"> - <img src="{{ STATIC_URL }}images/header-logo.jpg" border="0" alt="Madeira Logo" /> -</div> - -<div id="navleft" class="span-4 append-1"> - <ul> - <li><a href="{% url 'band.views.index' %}">Home</a></li> - <li><a href="{% url 'band.views.news' %}">News</a></li> - <li><a href="{% url 'band.views.bio' %}">Biography</a></li> - <li><a href="{% url 'band.views.gigs' %}">Shows</a></li> - <li><a href="{% url 'band.views.press_index' %}">Press</a></li> - <li><a href="{% url 'band.views.songs' %}">Songs</a></li> - <li><a href="{% url 'band.views.photos_index' %}">Photos</a></li> - <li><a href="{% url 'band.views.videos_index' %}">Videos</a></li> - <li><a href="{% url 'band.views.flyers' %}">Flyers</a></li> - <li><a href="{% url 'band.views.buy' %}">Buy</a></li> - <li><a href="{% url 'band.views.contact' %}">Contact</a></li> - <li><a href="{% url 'band.views.mail' %}">Mailing List</a></li> - <li><a href="http://myspace.com/themadeira">Myspace</a></li> - <li><a href="http://facebook.com/themadeira">Facebook</a></li> - <li><a href="http://www.youtube.com/user/TheMadeiraSurf">YouTube</a></li> - </ul> -</div> - -<div id="xxx-content" class="span-19 last"> - {% block content %} - {% endblock %} -</div> - -<div id="footer" class="span-24 last"> -Website © 2008 - 2011 by The Madeira <br /> -Visit <a href="http://myspace.com/themadeira">The Madeira on Myspace</a><br /> -and <a href="http://facebook.com/themadeira">The Madeira on Facebook</a><br /> -</div> - -</div> -</body> -</html>
--- a/mysite/templates/band/bio.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -{% extends 'band/base.html' %} -{% block title %}The Madeira | Biography{% endblock %} -{% block content %} -<h1>Band Biography</h1> -{% if members %} - {% for member in members %} - <h2>{{ member.name }} - {{ member.instrument }}</h2> - <p> - {% if member.photo %} - <img class="left" src="{{ member.photo.url }}" border="0" alt="{{ member.name }}" title="{{ member.name }}" /> - {{ member.bio|linebreaks }} - </p> - {% if member.gear_set.all %} - <p>Gear:</p> - <ul> - {% for item in member.gear_set.all %} - <li>{{ item.item }}</li> - {% endfor %} - </ul> - {% endif %} - {% endif %} - {% endfor %} -{% else %} -The band has no members. -{% endif %} -{% endblock %}
--- a/mysite/templates/band/buy.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -{% extends 'band/base.html' %} -{% block title %}The Madeira | Merchandise{% endblock %} -{% block content %} -<h1>Madeira Merchandise</h1> -{% for album in albums %} - <h2>{{ album.title }} </h2> - <p> - <img class="right" src="{{ album.photo.image.url }}" alt="{{ album.title }}" title="{{ album.title }}" /> - </p> - {% if album.label_release_set %} - <ul> - {% for release in album.label_release_set.all %} - <li><a href="{{ release.record_label.url }}">{{ release.record_label.name }}</a> - {{ release.catalog_number }}, {{ release.release_date|date:"F d, Y" }}</li> - {% endfor %} - </ul> - {% endif %} - {{ album.desc|safe|linebreaks }} - <p>Track listing:</p> - <ol> - {% for track in album.album_track_set.all %} - <li>{{ track.track_name }}</li> - {% endfor %} - </ol> - {% if album.album_merchant_set %} - <p>Buy {{ album.title }} at:</p> - <ul> - {% for merchant in album.album_merchant_set.all %} - <li><a href="{{ merchant.url }}">{{ merchant.name }}</a></li> - {% endfor %} - </ul> - {% endif %} - <br clear="all" /> -{% endfor %} -{% if merchandise %} - <hr /> - {{ config.ordering_info|safe|linebreaks }} -{% endif %} -{% for item in merchandise %} - <h2>{{ item.name }}</h2> - <p> - <img class="right" src="{{ item.photo.image.url }}" alt="{{ item.name }}" title="{{ item.name }}" /> - </p> - {{ item.desc|safe|linebreaks }} - {% if item.in_stock %} - <p>Price: ${{ item.price }}</p> - {% else %} - <p><strike>Price: ${{ item.price }}</strike> <strong>SOLD OUT!</strong></p> - {% endif %} - <br clear="all" /> -{% endfor %} -{% endblock %}
--- a/mysite/templates/band/contact.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -{% extends 'band/base.html' %} -{% block title %}The Madeira | Contact{% endblock %} -{% block content %} -<h1>Madeira Contact Info</h1> -<p>For general band inquiries, send email to: <a href="mailto:{{ config.contact_email }}">{{ config.contact_email }}</a>.</p> -<p>To contact individual band members:</p> -<ul> -{% for member in band %} - {% if member.email %} - <li>{{ member.name }}: <a href="mailto:{{ member.email }}">{{ member.email }}</a></li> - {% endif %} -{% endfor %} -</ul> -{% endblock %}
--- a/mysite/templates/band/email_subscribe.txt Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -Hello, - -We have received a request for this email address to join the mailing list -for {{ band }}. In order for us to process this subscription, we need confirmation from you. - -If you did not request to join this mailing list, you may ignore this message. - -To subscribe to the mailing list, go to the following confirmation URL: - -{{ url }} - -This should take you directly to an email confirmation page. If it does not, -please copy and paste the full URL into your web browser's address box and -hit the "Enter" key on your keyboard. - -Thanks, - -{{ band }} -{{ band_url }}
--- a/mysite/templates/band/email_unsubscribe.txt Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -Hello, - -We have received a request for this email address to unsubscribe from the mailing list -for {{ band }}. In order for us to process this request, we need confirmation from you. - -If you did not request to unsubscribe from this mailing list, you may ignore this message. - -To unsubscribe from the mailing list, go to the following confirmation URL: - -{{ url }} - -This should take you directly to an email confirmation page. If it does not, -please copy and paste the full URL into your web browser's address box and -hit the "Enter" key on your keyboard. - -Thanks, - -{{ band }} -{{ band_url }}
--- a/mysite/templates/band/flyers.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -{% extends 'band/base.html' %} -{% block title %}The Madeira | Flyer Gallery{% endblock %} -{% block content %} -<h1>Show Flyer Gallery</h1> -{% if gigs %} - <center> - {% for gig in gigs %} - <p> - {% if gig.title %} - <img src="{{ gig.flyer.image.url }}" alt="{{ gig.title }}" title="{{ gig.title }} : {{ gig.date|date:"F d, Y" }}" /> - {% else %} - <img src="{{ gig.flyer.image.url }}" alt="{{ gig.date|date:"F d, Y" }}" title="{{ gig.date|date:"F d, Y" }}" /> - {% endif %} - </p> - {% endfor %} - </center> -{% else %} -No flyers available at this time. -{% endif %} -{% endblock %}
--- a/mysite/templates/band/gigs.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +0,0 @@ -{% extends 'band/base.html' %} -{% load url from future %} -{% block title %}The Madeira | Shows{% endblock %} -{% block custom_css %} -<link rel="stylesheet" href="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" /> -{% endblock %} -{% block custom_js %} -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> -<script type="text/javascript" src="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.pack.js"></script> -<script type="text/javascript"> -$(function() { - $('a.fancybox').fancybox(); -}); -</script> -{% endblock %} -{% block content %} -<h1>Show Dates</h1> - -<h2>Upcoming Shows</h2> -{% if upcoming %} - {% for show in upcoming %} - <p style="clear:both"> - {% if show.flyer %} - <a href="{{ show.flyer.image.url }}" class="fancybox" rel="madeira-gallery"> - <!-- <img style="float:left; margin-right:5px; margin-bottom:1em" src="{{ show.flyer.get_thumbnail_url }}" --> - <img class="left" src="{{ show.flyer.get_thumbnail_url }}" - alt="{{ show.flyer.caption }}" title="{{ show.flyer.caption }}" /></a> - {% endif %} - <strong>{{ show.date|date:"F d, Y" }}</strong> - {% if show.time %}{{ show.time|time:"h:i A" }}{% endif %}<br /> - - {% if show.title and show.url %} - <a href="{{ show.url }}" target="_blank">{{ show.title }}</a><br /> - {% else %} - {% if show.title %} - {{ show.title }}<br /> - {% endif %} - {% endif %} - - {% if show.venue %} - {% if show.venue.url %} - <a href="{{ show.venue.url }}" target="_blank">{{ show.venue.name }}</a>, - {% else %} - {{ show.venue }}, - {% endif %} - {% if show.venue.address %} - {{ show.venue.address }}, - {% endif %} - {% if show.venue.city.state %} - {{ show.venue.city.name }}, {{ show.venue.city.state.name }} - {% else %} - {{ show.venue.city.name }} - {% endif %} - {% ifnotequal show.venue.city.country.name "USA" %} - {{ show.venue.city.country.name }} - {% endifnotequal %} - <br /> - {% if show.venue.phone %} - {{ show.venue.phone }} - <br /> - {% endif %} - {% endif %} - - {% if show.bands_ %} - With: - {% for band in show.bands_ %} - {% if band.url %} - <a href="{{ band.url }}" target="_blank">{{ band.name }}</a> - {% else %} - {{ band.name }} - {% endif %} - {% if not forloop.last %} - • - {% endif %} - {% endfor %} - <br /> - {% endif %} - - {% if show.notes %} - {{ show.notes|safe }} - {% endif %} - </p> - {% endfor %} -{% else %} -None at this time. -{% endif %} -<br clear="all" /> - -{% if flyerGigs %} -<div class="thumb-box"> - <h2>Flyers</h2> - <div style="width:90%; margin-left:auto;"> - {% for gig in flyerGigs %} - <div style="display:inline-table;"> - <table class="image-table"> - <caption>{{ gig.venue.name}}, {{ gig.date|date:"F 'y" }}</caption> - <tr><td> - <a href="{{ gig.flyer.image.url }}" class="fancybox" rel="madeira-gallery"> - <img src="{{ gig.flyer.get_thumbnail_url }}" alt="{{ gig.date|date:"F d, Y" }}" title="{{ gig.date|date:"F d, Y" }}" /></a> - </td></tr> - </table> - </div> - {% endfor %} - </div> - <div clear="all"></div> - <center><p>To see all our flyers in full size, check out our <a href="{% url 'band.views.flyers' %}">show flyer gallery</a>.</p></center> -</div> -{% endif %} - -{% if previous %} - <h2>Previous Shows</h2> - <center> - <table border="0" cellpadding="3" cellspacing="3" width="95%"> - <tr><th width="20%" align="center">Date</th><th width="40%" align="center">Venue</th><th width="40%" align="center">Bands</th></tr> - {% for show in previous %} - <tr> - <td width="20%">{{ show.date|date:"M d, Y" }}</td> - <td width="40%"> - {% if show.title and show.url %} - <a href="{{ show.url }}" target="_blank">{{ show.title }}</a>, - {% else %} - {% if show.title %} - {{ show.title }}, - {% endif %} - {% endif %} - {% if show.venue.url %} - <a href="{{ show.venue.url }}" target="_blank">{{ show.venue.name }}</a>, - {% else %} - {{ show.venue.name }}, - {% endif %} - {{ show.venue.city.name }}, {{ show.venue.city.state.abbrev }} - {% ifnotequal show.venue.city.country.name "USA" %} - {{ show.venue.city.country.name }} - {% endifnotequal %} - </td> - <td width="40%"> - {% for band in show.bands_ %} - {% if band.url %} - <a href="{{ band.url }}" target="_blank">{{ band.name }}</a> - {% else %} - {{ band.name }} - {% endif %} - {% if not forloop.last %} - • - {% endif %} - {% endfor %} - </td> - </tr> - {% endfor %} - </table> - </center> -{% endif %} - -{% if stats %} -<h2>Past Show Statistics</h2> -<table border="0" cellpadding="3" cellspacing="3"> - <tr><th align="left">Number of shows:</th><td>{{ stats.count }}</td></tr> - <tr><th align="left">Number of unique venues:</th><td>{{ stats.venues }}</td></tr> - <tr><th align="left">Number of unique cities:</th><td>{{ stats.cities }}</td></tr> - <tr><th align="left">Number of unique states:</th><td>{{ stats.states }}</td></tr> - <tr><th align="left">Number of unique countries:</th><td>{{ stats.countries }}</td></tr> - <tr><th align="left">Number of unique bands:</th><td>{{ stats.bands }}</td></tr> -</table> -{% endif %} - -{% endblock %}
--- a/mysite/templates/band/index.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -{% extends 'band/base.html' %} -{% load url from future %} -{% block title %}The Madeira{% endblock %} -{% block custom_css %} -<link rel="stylesheet" href="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" /> -{% endblock %} -{% block custom_js %} -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> -<script type="text/javascript" src="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.pack.js"></script> -<script type="text/javascript"> -$(function() { - $('a.fancybox').fancybox(); -}); -</script> -{% endblock %} -{% load markup %} -{% block content %} -<h1>The Madeira</h1> -<img class="floatLeftBox" src="{{ config.intro_photo.image.url }}" - alt="{{ config.intro_photo.title }}" title="{{config.intro_photo.title}}" border="0" /> -{{ config.intro_text|textile }} -<br /> - -{% if upcomingDates %} -<div class="center-block"> -<h2>Upcoming Shows...</h2> - -<center><table border="0" cellspacing="10" cellpadding="3"><tr> -{% for gig in upcomingDates %} - {% if gig.flyer %} - <td> - <a href="{{ gig.flyer.image.url }}" class="fancybox" rel="madeira-gallery"> - <img src="{{ gig.flyer.get_thumbnail_url }}" alt="{{ gig.flyer.caption }}" title="{{ gig.flyer.caption }}" /></a> - <br /><center>{{ gig.flyer.caption }}</center> - </td> - {% endif %} -{% endfor %} -</tr></table></center> - -<ul> -{% for show in upcomingDates %} -<li><strong>{{ show.date|date:"l, F d" }}</strong>: {{ show.venue.name }}, {{ show.venue.city.name }}{% if show.venue.city.state %}, {{ show.venue.city.state.name }} -{% endif %} -{% ifnotequal show.venue.city.country.name "USA" %} -{{ show.venue.city.country.name }} -{% endifnotequal %} -</li> -{% endfor %} -</ul> -<center><a href="{% url 'band.views.gigs' %}">See all upcoming shows...</a></center> -</div> -{% endif %} - -<div> - <center> - <h2>Sandstorm from Sound of the Surf</h2> -<object width="640" height="390"><param name="movie" value="http://www.youtube-nocookie.com/v/IFnyaCPyJSk?fs=1&hl=en_US&rel=0"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube-nocookie.com/v/IFnyaCPyJSk?fs=1&hl=en_US&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="390"></embed></object> -<p>Another clip from the upcoming film <a href="http://soundofthesurf.com">Sound of the Surf</a> has just been released, and it is our performance of Sandstorm! This movie cannot come out soon enough!</p> -</center> -</div> -<div> - <center> - <h2>New Song Preview!</h2> -<object width="480" height="385"><param name="movie" value="http://www.youtube.com/p/B67E923C98CCECD8?hl=en_US&fs=1"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/p/B67E923C98CCECD8?hl=en_US&fs=1" type="application/x-shockwave-flash" width="480" height="385" allowscriptaccess="always" allowfullscreen="true"></embed></object> - <p>Check out this set of 6 videos from our show at Mahogany's in February. Five of the songs are new originals slated for our new album! Video courtesy of TikiTim.</p> - </center> -</div> - -<div class="newsflash"> - <center> - <table border="0" cellspacing="2" cellpadding="2" width="100%"> - <tr><td colspan="2"><center><h1>The Madeira Releases:</h1></center></td></tr> - - <tr><td colspan="2"><center> - <img src="{{ carpe.image.url }}" alt="Carpe Noctem Cover" title="Carpe Noctem" border="0" /> - </center></td></tr> - <tr><td colspan="2"><center><strong>Available Now: Carpe Noctem!</strong><br /> - </center></td></tr> - <tr><td><center><a href="http://www.dblcrown.com"><img src="{{ sandstorm.image.url }}" alt="Sandstorm CD Cover" - title="Sandstorm" border="0" /></a></center></td> - - <td><center><a href="http://www.dblcrown.com"><img src="{{ ruins.image.url }}" alt="Ruins EP Cover" title="Ruins" - border="0" /></a></center></td></tr> - <tr><td><center><a href="http://www.dblcrown.com">Sandstorm</a></center></td> - <td><center><a href="http://www.dblcrown.com">Ruins</a></center></td></tr> - </table> - </center> -</div> - -{% endblock %}
--- a/mysite/templates/band/mail.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -{% extends 'band/base.html' %} -{% block title %}The Madeira | Mailing List{% endblock %} -{% block content %} -<h1>Madeira Mailing List</h1> -<p>Get on the Madeira mailing list to receive updates about upcoming shows, releases, and website updates. -This is a low volume list. We do not share your email address with anyone.</p> -<fieldset><legend>Mailing List</legend> - <form method="post" action="{{ request.build_absolute_uri }}">{% csrf_token %} - <table border="0" class="input-form"> - {% for field in form %} - <tr> - <th>{{ field.label_tag }}{% if field.field.required %}*{% endif %}:</th> - <td>{{ field }} - {% if field.errors %}{{ field.errors }}{% endif %}</td> - </tr> - {% endfor %} - <tr><td><input type="submit" name="Submit" value="Submit" class="submit-button" /></td></tr> - </table> - </form> -</fieldset> -{% endblock %}
--- a/mysite/templates/band/mail_confirm.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -{% extends 'band/base.html' %} -{% block title %}The Madeira | Mailing List Confirmation{% endblock %} -{% block content %} -<h1>Madeira Mailing List Confirmation</h1> -<p>Your email address, {{ email }}, has been successfully {{ action }}.</p> -{% endblock %}
--- a/mysite/templates/band/mail_not_found.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -{% extends 'band/base.html' %} -{% load url from future %} -{% block title %}The Madeira | Mailing List Confirmation{% endblock %} -{% block content %} -<h1>Madeira Mailing List</h1> -<p>Sorry, we did not find that email address in our database.</p> -<p>Back to <a href="{% url 'band.views.contact' %}">contact page</a>.</p> -{% endblock %}
--- a/mysite/templates/band/mail_thanks.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -{% extends 'band/base.html' %} -{% block title %}The Madeira | Mailing List Confirmation{% endblock %} -{% block content %} -<h1>Madeira Mailing List</h1> -<p>Thanks for subscribing to our email list! You should shortly receive a confirmation email -with instructions on how to complete the subscription process.</p> -<p><strong>Please check your spam folders for this email</strong>. Sometimes it ends up in there. -Thanks.</p> -{% endblock %}
--- a/mysite/templates/band/mail_unsubscribe.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -{% extends 'band/base.html' %} -{% block title %}The Madeira | Mailing List Confirmation{% endblock %} -{% block content %} -<h1>Madeira Mailing List</h1> -<p>We're sorry to see you unsubscribing from our email list! You should shortly receive a confirmation email -with instructions on how to complete the removal process. <strong>Please check your spam folders for -this email</strong>.</p> -{% endblock %}
--- a/mysite/templates/band/news.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -{% extends 'band/base.html' %} -{% load markup %} -{% block title %}The Madeira | News{% endblock %} -{% block content %} -<h1>News</h1> -{% if news %} - {% for story in news %} - <h2>{{ story.date|date:"F d, Y" }} - {% if story.title %} - • {{ story.title }} - {% endif %} - </h2> - <div> - {% if story.photo %} - <img src="{{ story.photo.url }}" class="floatLeftBox" - alt="{{ story.photo_caption }}" title="{{ story.photo_caption }}" border="0" /> - {% endif %} - {% if story.markup_enabled %} - {{ story.text|textile }} - {% else %} - {{ story.text|safe|linebreaks }} - {% endif %} - {% if story.author %} - <p><em>-- {{ story.author }}</em></p> - {% endif %} - </div> - {% endfor %} -{% else %} -No news at this time. -{% endif %} -{% endblock %}
--- a/mysite/templates/band/photo_detail.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -{% extends 'band/base.html' %} -{% load url from future %} -{% load markup %} -{% block title %}The Madeira | Photos: {{ gallery.title }}{% endblock %} -{% block custom_css %} -<link rel="stylesheet" href="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" /> -{% endblock %} -{% block custom_js %} -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> -<script type="text/javascript" src="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.pack.js"></script> -<script type="text/javascript"> -$(function() { - $('a.fancybox').fancybox(); -}); -</script> -{% endblock %} -{% block content %} -<h1>Madeira Photos: {{ gallery.title }}</h1> -{{ gallery.description|textile }} - -<div class="madeira-photo-list"> -{% for photo in photos %} - <a href="{{ photo.image.url }}" class="fancybox" rel="madeira-gallery"> - <img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.caption }}" title="{{ photo.caption }}" /></a> -{% endfor %} -</div> -<center><a href="{% url 'band.views.photos_index' %}">Photo gallery index</a></center> - -{% endblock %}
--- a/mysite/templates/band/photos.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -{% extends 'band/base.html' %} -{% load url from future %} -{% block title %}The Madeira | Photo Galleries{% endblock %} -{% block custom_css %} -<link rel="stylesheet" href="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" /> -{% endblock %} -{% block custom_js %} -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> -<script type="text/javascript" src="{{ STATIC_URL }}js/fancybox/jquery.fancybox-1.3.1.pack.js"></script> -<script type="text/javascript"> -$(function() { - $('a.fancybox').fancybox(); -}); -</script> -{% endblock %} -{% block content %} -<h1>Madeira Photo Galleries</h1> -{% if galleries %} - <ul> - {% for gallery in galleries %} - <li><a href="{% url 'band.views.photo_detail' gallery.id %}">{{ gallery.title }}</a></li> - {% endfor %} - </ul> -{% else %} -No photo galleries available at this time. -{% endif %} -{% if randomPhotos %} - <div class="madeira-photo-list"> - <h2>Random Photos:</h2> - {% for photo in randomPhotos %} - <a href="{{ photo.image.url }}" class="fancybox" rel="madeira-gallery"> - <img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.caption }}" title="{{ photo.caption }}" /></a> - {% endfor %} - </div> -{% endif %} -{% endblock %}
--- a/mysite/templates/band/press.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -{% extends 'band/base.html' %} -{% load markup %} -{% block title %}The Madeira | Press{% endblock %} -{% block content %} -<h1>Madeira Press, Articles, & Reviews</h1> -{% if articles %} - <a name="Contents"> </a> - <h2>Contents</h2> - <ul> - {% for article in articles %} - <li><a href="#article_{{ article.id }}">{{ article.title }}</a></li> - {% endfor %} - </ul> - - {% for article in articles %} - <a name="article_{{ article.id }}"> </a> - <h2>{{ article.title }}</h2> - {% if article.markup_enabled %} - {{ article.text|textile }} - {% else %} - {{ article.text|safe|linebreaks }} - {% endif %} - <div class="article-source"> - {{ article.source|safe|linebreaks }} - </div> - {% if article.url %} - <a href="{{ article.url }}" target="_blank">Original article</a> - {% endif %} - {% if article.pdf and article.url %} - | - {% endif %} - {% if article.pdf %} - <a href="{{ article.pdf.url }}" target="_blank">Original article as PDF</a> - <a href="http://www.adobe.com/products/acrobat/readstep2.html"> - <img src="{{ STATIC_URL }}images/get_adobe_reader.gif" alt="Adobe Reader" title="Get Adobe Reader" border="0" - align="middle" /></a> - {% endif %} - <p><a class="intLink" href="#Contents">Top</a></p> - {% endfor %} - -{% else %} -No articles at this time. -{% endif %} -{% endblock %}
--- a/mysite/templates/band/press_detail.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -{% extends 'band/base.html' %} -{% load url from future %} -{% load markup %} -{% block title %}The Madeira | Press{% endblock %} -{% block content %} -<h1>Madeira Press, Articles, & Reviews</h1> -<h2>{{ article.title }}</h2> -{% if article.markup_enabled %} - {{ article.text|textile }} -{% else %} - {{ article.text|safe|linebreaks }} -{% endif %} -<div class="article-source"> -{{ article.source|safe|linebreaks }} -</div> -{% if article.url %} -<a href="{{ article.url }}" target="_blank">Original article</a> -{% endif %} -{% if article.pdf and article.url %} -| -{% endif %} -{% if article.pdf %} -<a href="{{ article.get_pdf_url }}" target="_blank">Original article as PDF</a> -<a href="http://www.adobe.com/products/acrobat/readstep2.html"> - <img src="{{ STATIC_URL }}images/get_adobe_reader.gif" alt="Adobe Reader" title="Get Adobe Reader" border="0" - align="middle" /></a> -{% endif %} -<p><a href="{% url 'band.views.press_index' %}">Press index</a></p> -{% endblock %}
--- a/mysite/templates/band/songs.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -{% extends 'band/base.html' %} -{% block title %}The Madeira | Songs{% endblock %} -{% block content %} -<h1>Madeira Songs</h1> -{% if mp3Sets %} - <p>Check out some Madeira MP3 downloads!</p> - {% for set in mp3Sets %} - <h2>{{ set.title }}</h2> - {{ set.text|safe|linebreaks }} - <ul> - {% for mp3 in set.mp3_set.all %} - <li><a href="{{ mp3.file.url }}">{{ mp3.title }}</a> - ({{ mp3.file.size|filesizeformat }}){% if mp3.desc %} - {{ mp3.desc }}{% endif %}</li> - {% endfor %} - </ul> - {% endfor %} -{% else %} -No downloads available at this time. -{% endif %} -{% endblock %}
--- a/mysite/templates/band/video_detail.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -{% extends 'band/base.html' %} -{% load url from future %} -{% block title %}The Madeira | Videos: {{ vidset.title }}{% endblock %} -{% block content %} -<h1>Madeira Videos: {{ vidset.title }}</h1> -{{ vidset.text|safe|linebreaks }} - -<div align="center"> -<table cellspacing="3" cellpadding="2" border="0"> -{% for video in vidset.video_set.all %} - <tr><th>{{ video.title }}</th><td>{{ video.embed_code|safe }}</td></tr> -{% endfor %} -</table> -</div> -<br /> -<center><a href="{% url 'band.views.videos_index' %}">Videos index</a></center> - -{% endblock %}
--- a/mysite/templates/band/videos.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -{% extends 'band/base.html' %} -{% load url from future %} -{% block title %}The Madeira | Videos{% endblock %} -{% block content %} -<h1>Madeira Videos</h1> -{% if vidsets %} - <ul> - {% for set in vidsets %} - <li><a href="{% url 'band.views.video_detail' set.id %}">{{ set.title }}</a></li> - {% endfor %} - </ul> -{% else %} -No videos available at this time. -{% endif %} -{% endblock %}
--- a/mysite/templates/flatpages/default.html Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -{% extends 'band/base.html' %} -{% block title %}The Madeira | {{ flatpage.title }}{% endblock %} -{% block content %} -{{ flatpage.content }} -{% endblock %}
--- a/mysite/urls.py Tue Feb 14 19:09:57 2012 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -from django.conf.urls.defaults import * -from django.contrib import admin -from django.conf import settings - -admin.autodiscover() - -urlpatterns = patterns('', - (r'^', include('band.urls')), - (r'^admin/doc/', include('django.contrib.admindocs.urls')), - (r'^admin/', include(admin.site.urls)), - (r'^photologue/', include('photologue.urls')), -) - -if settings.DEBUG: - urlpatterns += patterns('', - (r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}), - )