Events und Gallery in einzelne Apps aufgetielt.
Lokalisierung auf einzelne Apps aufgeteilt Mai-Star Ranking in die Navigation integriert
This commit is contained in:
1
gallery/__init__.py
Normal file
1
gallery/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'christian'
|
||||
57
gallery/forms.py
Normal file
57
gallery/forms.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
Created on 03.10.2011
|
||||
|
||||
@author: christian
|
||||
"""
|
||||
from . import models
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth import get_user_model
|
||||
from utils.html5.widgets import DateTimeInput
|
||||
|
||||
user_query = get_user_model().objects.all()
|
||||
|
||||
|
||||
class PhotoUploadForm(forms.Form):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
photographer = forms.ModelChoiceField(user_query, required=True,)
|
||||
event = forms.ModelChoiceField(models.Event.objects.all(), required=True,)
|
||||
upload = forms.FileField(
|
||||
label=_('Images'),
|
||||
required=True,
|
||||
widget=forms.widgets.ClearableFileInput(
|
||||
attrs={
|
||||
'multiple': 'multiple',
|
||||
'accept': "image/gif,image/png,image/jpeg"
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class EditPhotoForm(forms.ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
class Meta(object):
|
||||
model = models.Photo
|
||||
fields = ('event', 'name', 'description', 'photographer',
|
||||
'anchor_horizontal', 'anchor_vertical',
|
||||
'created_date', 'on_startpage')
|
||||
|
||||
|
||||
class EventForm(forms.ModelForm):
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
|
||||
start = forms.DateTimeField(
|
||||
label=_('start'), required=True,
|
||||
widget=DateTimeInput() # widget=SplitDateTimeWidget()
|
||||
)
|
||||
end = forms.DateTimeField(
|
||||
label=_('end'), required=False,
|
||||
widget=DateTimeInput() # widget=SplitDateTimeWidget()
|
||||
)
|
||||
|
||||
class Meta(object):
|
||||
model = models.Event
|
||||
403
gallery/models.py
Normal file
403
gallery/models.py
Normal file
@@ -0,0 +1,403 @@
|
||||
# -'- Encoding: utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
from imagekit import ImageSpec
|
||||
import imagekit
|
||||
from imagekit.models import ImageSpecField
|
||||
from pilkit import processors
|
||||
import pyexiv2
|
||||
|
||||
from utils import COUNTRIES, OverwriteStorage
|
||||
|
||||
|
||||
CHOICES_HORIZONTAL = (
|
||||
(0.00000001, _('left')),
|
||||
(0.5, _('center')),
|
||||
(1, _('right'))
|
||||
)
|
||||
|
||||
CHOICES_VERTICAL = (
|
||||
(0.00000001, _('top')),
|
||||
(0.5, _('middle')),
|
||||
(1, _('bottom'))
|
||||
)
|
||||
|
||||
|
||||
def get_upload_path(instance, filename):
|
||||
"""
|
||||
Generates the desired file path and filename for an uploaded Image.
|
||||
With this function Django can save the uploaded images to subfolders that
|
||||
also have a meaning for humans.
|
||||
|
||||
@param instance: an Django Object for which the Image has been uploaded.
|
||||
@type instance: a instace of an models.Model sub-class.
|
||||
@param filename: The filename of the uploaded image.
|
||||
@type filename: String
|
||||
"""
|
||||
extension = filename[filename.rfind('.') + 1:]
|
||||
if isinstance(instance, Event):
|
||||
if instance.id:
|
||||
return "events/%s.%s" % (instance.id, extension)
|
||||
else:
|
||||
return "events/%s.%s" % (slugify(instance.name), extension)
|
||||
elif isinstance(instance, Location):
|
||||
if instance.id:
|
||||
return "events/location/%s.%s" % (instance.id, extension)
|
||||
else:
|
||||
return "events/location/%s.%s" % (instance.id, extension)
|
||||
elif isinstance(instance, Photo):
|
||||
return "events/%s/%s" % (instance.event.id, filename)
|
||||
|
||||
|
||||
def post_save_image(sender, instance=None, created=False, raw=False, **kwargs):
|
||||
"""
|
||||
Reganerate the images.
|
||||
"""
|
||||
os.remove(instance.display.path)
|
||||
os.remove(instance.callout.path)
|
||||
os.remove(instance.thumbnail.path)
|
||||
|
||||
"""
|
||||
instance.callout.generate(force=True)
|
||||
instance.display.generate(force=True)
|
||||
instance.thumbnail.generate(force=True)
|
||||
"""
|
||||
|
||||
|
||||
class CalloutImage(ImageSpec):
|
||||
format = 'PNG'
|
||||
width = 940
|
||||
height = 300
|
||||
|
||||
@property
|
||||
def processors(self):
|
||||
model, field_name = imagekit.utils.get_field_info(self.source) # @UnusedVariable @IgnorePep8
|
||||
anchor = model.get_anchor()
|
||||
if anchor:
|
||||
return [processors.Transpose(), processors.ResizeToFill(
|
||||
width=self.width,
|
||||
height=self.height, anchor=anchor
|
||||
)]
|
||||
else:
|
||||
return [processors.Transpose(), processors.SmartResize(
|
||||
width=self.width,
|
||||
height=self.height
|
||||
)]
|
||||
|
||||
|
||||
class DisplayImage(ImageSpec):
|
||||
format = 'PNG'
|
||||
processors = [processors.Transpose(),
|
||||
processors.ResizeToFit(width=940, height=940, upscale=False)]
|
||||
|
||||
|
||||
class ThumbnailImage(CalloutImage):
|
||||
format = 'PNG'
|
||||
width = 140
|
||||
height = 140
|
||||
|
||||
|
||||
imagekit.register.generator('kasu:image:callout', CalloutImage)
|
||||
imagekit.register.generator('kasu:image:display', DisplayImage)
|
||||
imagekit.register.generator('kasu:image:thumbnail', ThumbnailImage)
|
||||
|
||||
|
||||
class ImageModel(models.Model):
|
||||
callout = ImageSpecField(source='image', id='kasu:image:callout')
|
||||
display = ImageSpecField(source='image', id='kasu:image:display')
|
||||
thumbnail = ImageSpecField(source='image', id='kasu:image:thumbnail')
|
||||
|
||||
def get_anchor(self):
|
||||
try:
|
||||
anchor_horizontal = getattr(self, 'anchor_horizontal')
|
||||
anchor_vertical = getattr(self, 'anchor_vertical')
|
||||
except AttributeError:
|
||||
return None
|
||||
if anchor_horizontal and anchor_vertical:
|
||||
return self.anchor_horizontal, self.anchor_vertical
|
||||
else:
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class EventManager(models.Manager):
|
||||
def current_event(self):
|
||||
try:
|
||||
current = self.filter(start__lte=now())
|
||||
current = current.filter(end__gte=now())
|
||||
return current.order_by('start', 'end')[0]
|
||||
except:
|
||||
return None
|
||||
|
||||
def next_event(self):
|
||||
try:
|
||||
return self.filter(start__gt=now()).order_by('start', 'end')[0]
|
||||
except:
|
||||
return None
|
||||
|
||||
def archive(self):
|
||||
return self.filter(start__lt=now())
|
||||
|
||||
def upcoming(self, limit=3):
|
||||
result = self.filter(start__gt=now()).order_by('start', 'end')
|
||||
if limit:
|
||||
return result[1:(limit + 1)]
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
class Event(ImageModel):
|
||||
name = models.CharField(_('Name'), max_length=255)
|
||||
description = models.TextField(_("Description"), blank=True)
|
||||
location = models.ForeignKey('Location')
|
||||
start = models.DateTimeField(_('Start'))
|
||||
end = models.DateTimeField(_('End'), blank=True, null=True)
|
||||
url = models.URLField(_('Homepage'), blank=True)
|
||||
image = models.ImageField(_("Image"), upload_to=get_upload_path,
|
||||
storage=OverwriteStorage(), blank=True, null=True)
|
||||
is_tournament = models.BooleanField(_('Tournament'), default=False,
|
||||
help_text=_(u'This event is a tournament, different rules apply for \
|
||||
the kyu ranking.'))
|
||||
photo_count = models.PositiveIntegerField(default=0, editable=False)
|
||||
event_series = models.ForeignKey('Event', blank=True, null=True,
|
||||
on_delete=models.SET_NULL, editable=False,
|
||||
verbose_name=_('Event Series'),
|
||||
help_text=_(u'Wenn dieser Event zu einer Veranstaltungsreihe gehört \
|
||||
werden Ort, Beschreibung, Bild und Homepage von dem hier angegebenen \
|
||||
Event übernommen.'))
|
||||
objects = EventManager()
|
||||
|
||||
class Meta(object):
|
||||
verbose_name = _('Event')
|
||||
verbose_name_plural = _('Events')
|
||||
ordering = ('-start', '-end',)
|
||||
|
||||
def __unicode__(self):
|
||||
try:
|
||||
return "%(name)s (%(date)s)" % {'name': self.name,
|
||||
'date': self.start.date()}
|
||||
except:
|
||||
return "New Event Model"
|
||||
|
||||
def get_absolute_url(self):
|
||||
kwargs = {
|
||||
'pk': self.id,
|
||||
'year': self.start.strftime('%Y'),
|
||||
'month': self.start.strftime('%m')
|
||||
}
|
||||
return reverse('event-detail', kwargs=kwargs)
|
||||
|
||||
def get_edit_url(self):
|
||||
kwargs = {
|
||||
'pk': self.id,
|
||||
'year': self.start.strftime('%Y'),
|
||||
'month': self.start.strftime('%m')
|
||||
}
|
||||
return reverse('event-form', kwargs=kwargs)
|
||||
|
||||
def get_callout(self):
|
||||
if self.image:
|
||||
return self.callout
|
||||
elif self.photo_set.count():
|
||||
return self.photo_set.all().order_by('?')[0].callout
|
||||
elif self.location.image:
|
||||
return self.location.callout
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_thumbnail(self):
|
||||
if self.image:
|
||||
return self.thumbnail
|
||||
elif self.photo_set.count():
|
||||
return self.photo_set.all().order_by('?')[0].thumbnail
|
||||
elif self.location.image:
|
||||
return self.location.thumbnail
|
||||
else:
|
||||
return None
|
||||
|
||||
def save(self, **kwargs):
|
||||
if self.event_series:
|
||||
master_event = self.event_series
|
||||
self.description = master_event.description
|
||||
self.location = master_event.location
|
||||
self.url = master_event.url
|
||||
self.image = master_event.image
|
||||
self.photo_count = self.photo_set.count()
|
||||
models.Model.save(self, **kwargs)
|
||||
|
||||
# Update the rest of the event series:
|
||||
for sub_event in Event.objects.filter(event_series=self):
|
||||
sub_event.save()
|
||||
|
||||
# Update the Hanchans if necesery:
|
||||
for hanchan in self.hanchan_set.all():
|
||||
hanchan.save()
|
||||
|
||||
|
||||
class Location(ImageModel):
|
||||
name = models.CharField(_("Name"), max_length=200)
|
||||
description = models.TextField(_("Description"), blank=True)
|
||||
image = models.ImageField(_("Image"), upload_to=get_upload_path,
|
||||
storage=OverwriteStorage(), blank=True, null=True)
|
||||
url = models.URLField(_('Homepage'), blank=True)
|
||||
postal_code = models.CharField(_('Postal Code'), max_length=6)
|
||||
street_address = models.CharField(_('Street Address'), max_length=127)
|
||||
locality = models.CharField(_('Locality'), max_length=127)
|
||||
country = models.CharField(_('Country'), max_length=2, choices=COUNTRIES)
|
||||
|
||||
class Meta(object):
|
||||
verbose_name = _('Venue')
|
||||
verbose_name_plural = _('Venues')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
address = (self.street_address, self.locality, self.country,)
|
||||
return ','.join(address)
|
||||
|
||||
|
||||
class PhotoManager(models.Manager):
|
||||
def get_random(self, startpage=True):
|
||||
if startpage:
|
||||
queryset = self.filter(on_startpage=True)
|
||||
else:
|
||||
queryset = self.all().order_by('?')[0]
|
||||
try:
|
||||
return queryset.order_by('?')[0]
|
||||
except IndexError:
|
||||
return Photo()
|
||||
|
||||
|
||||
class Photo(ImageModel):
|
||||
name = models.CharField(_("Name"), max_length=100, blank=True)
|
||||
image = models.ImageField(_("Image"), upload_to=get_upload_path,
|
||||
storage=OverwriteStorage())
|
||||
anchor_horizontal = models.FloatField(
|
||||
_('horizontal Anchorpoint'),
|
||||
choices=CHOICES_HORIZONTAL,
|
||||
blank=True, null=True,
|
||||
help_text='Der Ankerpunkt ist der interessante Teil des Bildes,\
|
||||
welcher nie abgeschnitten werden darf'
|
||||
)
|
||||
anchor_vertical = models.FloatField(
|
||||
_('vertical Anchorpoint'),
|
||||
choices=CHOICES_VERTICAL,
|
||||
blank=True, null=True,
|
||||
help_text='Wenn kein Ankerpunkt von Hand (horizontal und vertikal)\
|
||||
festgelegt wird, versucht die Software diesen selbst zu erraten.'
|
||||
)
|
||||
|
||||
event = models.ForeignKey(Event)
|
||||
description = models.TextField(
|
||||
_("Description"),
|
||||
max_length=300,
|
||||
blank=True
|
||||
)
|
||||
photographer = models.ForeignKey(settings.AUTH_USER_MODEL)
|
||||
on_startpage = models.BooleanField(
|
||||
_("Startpage"),
|
||||
default=False,
|
||||
help_text=_('Display this Photo on the Startpage Teaser')
|
||||
)
|
||||
created_date = models.DateTimeField(_("Published on"))
|
||||
views = models.PositiveIntegerField(
|
||||
_("Number of views"),
|
||||
editable=False,
|
||||
default=0
|
||||
)
|
||||
objects = PhotoManager()
|
||||
metadata = None
|
||||
orientation = 1
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Event Image')
|
||||
verbose_name_plural = _('Event Images')
|
||||
ordering = ["created_date"]
|
||||
get_latest_by = "created_date"
|
||||
|
||||
def __unicode__(self):
|
||||
return os.path.basename(self.image.name)
|
||||
|
||||
def read_metadata(self):
|
||||
image_path = os.path.join(settings.MEDIA_ROOT, self.image.name)
|
||||
self.metadata = pyexiv2.ImageMetadata(image_path)
|
||||
self.metadata.read()
|
||||
try:
|
||||
self.orientation = self.metadata['Exif.Image.Orientation'].value
|
||||
except:
|
||||
self.orientation = 1
|
||||
|
||||
def save_metadata(self):
|
||||
if not self.metadata:
|
||||
self.read_metadata()
|
||||
self.metadata['Exif.Image.DateTime'] = self.created_date
|
||||
self.metadata['Exif.Image.ImageDescription'] = self.description
|
||||
self.metadata['Exif.Image.Artist'] = self.photographer.username
|
||||
self.metadata['Exif.Image.Orientation'] = self.orientation or 1
|
||||
self.metadata.write()
|
||||
|
||||
def rotate(self, rotate):
|
||||
"""
|
||||
Sets an the Exif tag in an image to set the right direction.
|
||||
This provides lossless image rotation.
|
||||
@param rotate: 'clockwise' or 'counter-clockwise' the direction in
|
||||
which we should rotate the image in 90° steps.
|
||||
"""
|
||||
if not self.metadata:
|
||||
self.read_metadata()
|
||||
if rotate == 'clockwise':
|
||||
if self.orientation == 1:
|
||||
self.orientation = 6
|
||||
elif self.orientation == 6:
|
||||
self.orientation = 3
|
||||
elif self.orientation == 3:
|
||||
self.orientation = 8
|
||||
else:
|
||||
self.orientation = 1
|
||||
elif rotate == 'counter-clockwise':
|
||||
if self.orientation == 1:
|
||||
self.orientation = 8
|
||||
elif self.orientation == 8:
|
||||
self.orientation = 3
|
||||
elif self.orientation == 3:
|
||||
self.orientation = 6
|
||||
else:
|
||||
self.orientation = 1
|
||||
self.save()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
'event-photo',
|
||||
kwargs={'event': self.event.id, 'pk': self.id}
|
||||
)
|
||||
|
||||
@property
|
||||
def next_photo(self):
|
||||
return self.get_next_by_created_date(event=self.event)
|
||||
|
||||
@property
|
||||
def previous_photo(self):
|
||||
return self.get_previous_by_created_date(event=self.event)
|
||||
|
||||
def save(self, **kwargs):
|
||||
"""
|
||||
Triggers to save related Event to save. This should force an update for
|
||||
the denormalized Photo count.
|
||||
"""
|
||||
ImageModel.save(self, **kwargs)
|
||||
self.save_metadata()
|
||||
self.event.save()
|
||||
|
||||
|
||||
models.signals.post_save.connect(post_save_image, sender=Photo)
|
||||
25
gallery/templates/gallery/photo_confirm_delete.html
Normal file
25
gallery/templates/gallery/photo_confirm_delete.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n comments %}
|
||||
|
||||
{% block maincontent %}
|
||||
<form action="" method="post" class="grid_12">
|
||||
{% csrf_token %}
|
||||
<header>
|
||||
<h1 class="grid_12">Dieses Photo wirklich löschen?</h1>
|
||||
</header>
|
||||
<p>Sind Sie sicher, dass Sie das Bild “{{photo.name}}” löschen wollen?</p>
|
||||
<img src="{{photo.display.url}}" alt="{{photo.name}}" title="{{photo.name}}" class="grid_10 push_1"/>
|
||||
<br class="clear"/>
|
||||
<p> </p>
|
||||
<p class="buttonbar">
|
||||
<a href="{% url 'event-photo-list' photo.event.id %}" class="button" style="float: left;"><img
|
||||
src="{{STATIC_URL}}icons/cancel.png" alt="{% trans 'Cancel' %}"/> {% trans 'Cancel' %}</a>
|
||||
<button type="submit"><img src="{{STATIC_URL}}icons/delete.png" alt="{% trans 'Delete' %}"/>
|
||||
{% trans 'Delete' %}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block buttonbar %}{% endblock %}
|
||||
|
||||
|
||||
56
gallery/templates/gallery/photo_detail.html
Normal file
56
gallery/templates/gallery/photo_detail.html
Normal file
@@ -0,0 +1,56 @@
|
||||
{% extends "events/photo_list.html" %}
|
||||
{% load i18n comments %}
|
||||
|
||||
{% block title %} {{ photo.name }} - {{ photo.event.name }} {% endblock %}
|
||||
|
||||
{% block opengraph %}
|
||||
<meta property="og:type" content="photo" />
|
||||
<meta property="og:title" content="{{photo.name}} - Foto" />
|
||||
<meta property="og:url" content="http://www.kasu.at{{photo.get_absolute_url}}" />
|
||||
<meta property="og:image" content="http://www.kasu.at{{photo.thumbnail.url}}" />
|
||||
{% if photo.description %}<meta property="og:description" content="{{photo.description}}" />{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
<h2 class="grid_12"><a href="{% url 'event-photo-list' photo.event.id %}">{{photo.event.name}}</a> » {{ photo.name }}</h2>
|
||||
<div id="display" class="grid_12 clearfix">
|
||||
<img src="{{photo.display.url}}" alt="{{photo.name}}" title="{{photo.name}}"/>
|
||||
{% if photo.previous_photo %}
|
||||
<a href="{{ photo.previous_photo.get_absolute_url }}" class="previous">{% trans 'previous' %}</a>
|
||||
{% endif %}
|
||||
{% if photo.next_photo %}
|
||||
<a href="{{ photo.next_photo.get_absolute_url }}" class="next">Next</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if perms.events.change_photo %}
|
||||
<form method="post" enctype="multipart/form-data" class="grid_12">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
{% include "form.html" %}
|
||||
<p class="buttonbar">
|
||||
<a href="{{ photo.image.url }}" class="button" type="application/octet-stream"><img src="{{ STATIC_URL}}icons/drive_go.png" alt="{% trans 'download' %}" title="{% trans 'download' %}" /></a>
|
||||
<button type="submit" name="rotate" value="counter-clockwise"><img src="{{STATIC_URL}}icons/shape_rotate_anticlockwise.png" title="Gegen den Uhrzeiger drehen"></button>
|
||||
<button type="submit" name="rotate" value="clockwise"><img src="{{STATIC_URL}}icons/shape_rotate_clockwise.png" title="Im Uhrzeiger drehen"></button>
|
||||
<button type="submit"><img src="{{STATIC_URL}}icons/disk.png" alt=""> {% trans "save" %}</button>
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="grid_10 push_1">{{ photo.description }}</p>
|
||||
|
||||
<ul class="info grid_11 push_1">
|
||||
<li class="user"><strong>{% trans 'Photographer' %}: </strong>{{ photo.photographer }}</li>
|
||||
<li class="date"><strong>{% trans 'on' %}</strong> {{ photo.created_date }}</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="grid_12 more_link">
|
||||
<a href="https://m.google.com/app/plus/x/?v=compose&content={{photo.headline|urlencode}}+-+http%3A%2F%2Fwww.kasu.at/{{photo.get_absolute_url|urlencode}}" target="_blank"><img src="{{STATIC_URL}}img/google_plus.png" alt="Google+" title="{% trans 'Share on Google+'%}" /></a>
|
||||
<a href="https://twitter.com/share?url=http%3A%2F%2Fwww.kasu.at/{{photo.get_absolute_url|urlencode}}" target='_blank'><img src="{{STATIC_URL}}img/twitter.png" alt="Twitter" title="{% trans 'Share on Twitter' %}" /></a>
|
||||
<a href="http://facebook.com/sharer.php?u=http%3A%2F%2Fwww.kasu.at{{photo.get_absolute_url|urlencode}}" target="_blank"><img src="{{STATIC_URL}}img/facebook.png" alt="Facebook" title="{% trans 'Share on Facebook'%}" /></a>
|
||||
</div>
|
||||
{% render_comment_list for photo %}
|
||||
{% render_comment_form for photo %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
37
gallery/templates/gallery/photo_gallery.html
Normal file
37
gallery/templates/gallery/photo_gallery.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block teaser %}<h2>{{page.title}}{% trans 'Photos' %}</h2>{% endblock %}
|
||||
|
||||
{% block navigation %}
|
||||
<ul id="navigation">
|
||||
{% if event %}
|
||||
<li><a href="{{ current_top_page.get_absolute_url }}">{{current_top_page.menu_name}}</a></li>
|
||||
<li><a class="active">{{event.name}}</a></li>
|
||||
{% elif photo %}
|
||||
<li><a href="{{ current_top_page.get_absolute_url }}">{{current_top_page.menu_name}}</a></li>
|
||||
<li><a href="{% url 'event-photo-list' photo.event.id %}">{{photo.event.name}}</a></li>
|
||||
<li><a class="active">{{photo.name}}</a></li>
|
||||
{% else %}
|
||||
<li><a href="{{ current_top_page.get_absolute_url }}" {% ifequal current_page current_top_page %}class="active"{% endifequal %}>{{current_top_page.menu_name}}</a></li>
|
||||
{% if perms.event.add_photo %}
|
||||
<li><a href="/gallery/upload/" class="{% ifequal current_path 'gallery/upload' %}active{% endifequal %}">{% trans 'Upload' %}</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
{% for event in event_list %}
|
||||
<div class="gallery grid_4">
|
||||
<h3><a href="{% url 'event-photo-list' event.id %}">{{event.name}}</a></h3>
|
||||
<a href="{% url 'event-photo-list' event.id %}"><img src="{{event.get_thumbnail.url}}" class="thumbnail" alt="{{ event.name }}"/></a>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p>Sorry da kommt erst was hin!</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block buttonbar %}{% endblock %}
|
||||
|
||||
|
||||
47
gallery/templates/gallery/photo_list.html
Normal file
47
gallery/templates/gallery/photo_list.html
Normal file
@@ -0,0 +1,47 @@
|
||||
{% extends "events/event_detail.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{event.name}}{% endblock %}
|
||||
|
||||
{% block opengraph %}
|
||||
<meta property="og:type" content="album" />
|
||||
<meta property="og:title" content="{{event.name}}" />
|
||||
<meta property="og:url" content="http://www.kasu.at{% url 'event-photo-list' event.pk %}" />
|
||||
<meta property="og:image" content="http://www.kasu.at{{ event.get_thumbnail.url }}" />
|
||||
{% if event.description %}<meta property="og:description" content="{{event.description}}" />{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
{% if perms.events.delete_photo %}
|
||||
{% for photo in photo_list %}
|
||||
<div class="thumbnail">
|
||||
<a href="{{photo.get_absolute_url}}"><img src="{{photo.thumbnail.url}}" alt=""/></a>
|
||||
<a href="{% url 'delete-event-photo' photo.pk %}" class="delete_image"><img src="{{STATIC_URL}}icons/delete.png" title="{% trans 'delete' %}"/></a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for photo in photo_list %}
|
||||
<a href="{{photo.get_absolute_url}}" class="thumbnail"><img src="{{photo.thumbnail.url}}" alt=""/></a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if perms.events.add_photo %}
|
||||
<br class="clear" />
|
||||
<form action="{% url 'event-photo-upload' event.id %}" method="post" enctype="multipart/form-data" class="grid_12">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>Photos hochladen</legend>
|
||||
{% include "form.html"%}
|
||||
<p class="buttonbar">
|
||||
<button type="reset"><img src="{{STATIC_URL}}icons/arrow_undo.png" alt="{% trans 'reset' %}" /> {% trans 'reset' %}</button>
|
||||
<button type="submit"><img src="{{STATIC_URL}}icons/image_add.png" alt="{% trans 'upload' %}" /> {% trans 'upload' %}</button>
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block buttonbar %}{% endblock %}
|
||||
|
||||
|
||||
54
gallery/templates/gallery/photo_upload.html
Normal file
54
gallery/templates/gallery/photo_upload.html
Normal file
@@ -0,0 +1,54 @@
|
||||
{% extends "events/photo_gallery.html" %}
|
||||
{% load i18n comments %}
|
||||
|
||||
{% block maincontent %}
|
||||
<h2 class="grid_12">Photos</a> » {% trans "upload"%}</h2>
|
||||
{% for event in event_list %}
|
||||
{% get_comment_count for event as comment_count %}
|
||||
{% ifchanged %}<h3 class="grid_12">{{ event.start|date:'F Y' }}</h3>{% endifchanged %}
|
||||
<div style="float:left">
|
||||
<a href="{% url 'event-photo-list' event.pk %}"><img src="{{ event.get_thumbnail.url }}" alt="" class="thumbnail"/></a>
|
||||
<div class="grid_4" />
|
||||
<h4><a href="{% url 'event-photo-list' event.pk %}">{{ event.name }}</a></h4>
|
||||
<div class="info">
|
||||
<img src="{{ STATIC_URL }}/icons/date.png" alt="{% trans 'Start' %}" title="{% trans 'Start' %}">
|
||||
{{ event.start|date }}
|
||||
{% if event.end %}
|
||||
{% trans "from" %} {{ event.start|time:'H:i' }} {% trans "to" %} {{ event.end|time:'H:i' }}
|
||||
{% else %}
|
||||
{{ event.start|time:'H:i' }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if event.description %}<p>{{event.description}}</p>{% endif %}
|
||||
<div class="info">
|
||||
<img src="{{ STATIC_URL }}/icons/map.png" alt="{% trans 'Location' %}" title="{% trans 'Location' %}">
|
||||
{{ event.location }}
|
||||
<img src="{{ STATIC_URL }}/icons/comments.png" alt="{% trans 'Comments' %}" title="{% trans 'Comments' %}">
|
||||
<a href="{{event.get_absolute_url}}#comments">{{ comment_count }} {% trans 'Comments' %}</a>
|
||||
<img src="{{ STATIC_URL }}/icons/images.png" alt="{% trans 'Photos' %}" title="{% trans 'Photos' %}">
|
||||
<a href="{% url 'event-photo-list' event.pk %}">{{ event.photo_count }} {% trans 'Photos' %}</a>
|
||||
</div>
|
||||
<p style="text-align:right">
|
||||
{% if perms.events.add_photo %}
|
||||
<a href="{% url 'event-photo-list' event.pk %}" class="button"><img src="{{ STATIC_URL }}icons/image_add.png" alt="{%trans "Upload" %}"></a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div></div>
|
||||
{% endfor %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset class="grid_8 push_2">
|
||||
<legend>Photos hochladen</legend>
|
||||
{% include "form.html" %}
|
||||
<p class="buttonbar">
|
||||
<button type="submit">
|
||||
<img src="{{ STATIC_URL }}icons/drive_go.png" alt="" />
|
||||
{% trans "upload" %}
|
||||
</button>
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
0
gallery/urls.py
Normal file
0
gallery/urls.py
Normal file
247
gallery/views.py
Normal file
247
gallery/views.py
Normal file
@@ -0,0 +1,247 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Create your views here.
|
||||
from datetime import timedelta
|
||||
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import redirect
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views import generic
|
||||
from icalendar import Calendar, Event
|
||||
import pyexiv2
|
||||
|
||||
from utils.mixins import PermissionRequiredMixin
|
||||
|
||||
from . import models, forms
|
||||
|
||||
|
||||
class DeleteEventPhoto(generic.DeleteView):
|
||||
model = models.Photo
|
||||
"""
|
||||
def get_object(self, queryset=None):
|
||||
return models.Photo.objects.get(pk=self.kwargs['pk'])
|
||||
"""
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('event-photo-list', args=[self.object.event.id])
|
||||
|
||||
@method_decorator(permission_required('events.delete_photo'))
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(DeleteEventPhoto, self).dispatch(*args, **kwargs)
|
||||
|
||||
|
||||
class EventArchiveIndex(generic.ArchiveIndexView):
|
||||
allow_empty = True
|
||||
context_object_name = 'event_list'
|
||||
date_field = 'start'
|
||||
model = models.Event
|
||||
queryset = model.objects.all()
|
||||
paginate_by = 15
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = generic.ArchiveIndexView.get_context_data(self, **kwargs)
|
||||
context['is_archive'] = True
|
||||
return context
|
||||
|
||||
|
||||
class EventArchiveMonth(generic.MonthArchiveView):
|
||||
date_field = 'start'
|
||||
make_object_list = True
|
||||
model = models.Event
|
||||
month_format = '%m'
|
||||
paginate_by = 15
|
||||
template_name = 'events/event_archive.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = generic.MonthArchiveView.get_context_data(self, **kwargs)
|
||||
context['is_archive'] = True
|
||||
return context
|
||||
|
||||
|
||||
class EventArchiveYear(generic.YearArchiveView):
|
||||
date_field = 'start'
|
||||
make_object_list = True
|
||||
model = models.Event
|
||||
paginate_by = 15
|
||||
template_name = 'events/event_archive.html'
|
||||
year_format = '%Y'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = generic.YearArchiveView.get_context_data(self, **kwargs)
|
||||
context['is_archive'] = True
|
||||
return context
|
||||
|
||||
|
||||
class EventDetail(generic.DetailView):
|
||||
model = models.Event
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = generic.DetailView.get_context_data(self, **kwargs)
|
||||
context['form'] = forms.PhotoUploadForm(initial={'event': self.object, 'photographer': self.request.user})
|
||||
return context
|
||||
|
||||
|
||||
class EventForm(PermissionRequiredMixin, generic.UpdateView):
|
||||
form_class = forms.EventForm
|
||||
permission_required = 'events.add_event'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
"""
|
||||
If an id has been submitted, try return the existing Event for an update,
|
||||
else creates a new one.
|
||||
@param queryset:
|
||||
"""
|
||||
if self.kwargs.get('pk'):
|
||||
event = models.Event.objects.get(pk=self.kwargs['pk'])
|
||||
if event.event_series:
|
||||
return event.event_series
|
||||
else:
|
||||
return event
|
||||
else:
|
||||
return models.Event()
|
||||
|
||||
|
||||
class EventGallery(generic.ListView):
|
||||
template_name = 'events/photo_gallery.html'
|
||||
queryset = models.Event.objects.filter(start__lt=timezone.now(), photo_count__gt=0)
|
||||
paginate_by = 12
|
||||
|
||||
|
||||
class EventListIcal(generic.View):
|
||||
"""
|
||||
Generates an returns an iCal File with all upcoming events.
|
||||
"""
|
||||
|
||||
def add_event(self, event):
|
||||
ics_event = Event()
|
||||
dtstart = timezone.localtime(event.start)
|
||||
dtend = timezone.localtime(event.end)
|
||||
|
||||
ics_event.add('DTSTART', dtstart)
|
||||
ics_event.add('SUMMARY', event.name)
|
||||
ics_event.add('DESCRIPTION', event.description)
|
||||
ics_event.add('LOCATION', event.location.address)
|
||||
ics_event.add('URL', 'http://www.kasu.at' + event.get_absolute_url())
|
||||
ics_event['UID'] = 'event-%d@www.kasu.at' % event.pk
|
||||
ics_event.add('PRIORITY', 5)
|
||||
if event.end:
|
||||
ics_event.add('DTEND', dtend)
|
||||
self.calendar.add_component(ics_event)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
response = HttpResponse(mimetype="text/calendar; charset=UTF-8")
|
||||
self.calendar = Calendar()
|
||||
self.calendar.add('prodid', 'http://www.kasu.at/')
|
||||
self.calendar.add('version', '2.0')
|
||||
for event in models.Event.objects.upcoming(limit=None):
|
||||
self.add_event(event)
|
||||
response.write(self.calendar.to_ical())
|
||||
return response
|
||||
|
||||
|
||||
class EventPhoto(generic.UpdateView):
|
||||
form_class = forms.EditPhotoForm
|
||||
model = models.Photo
|
||||
template_name = 'events/photo_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventPhoto, self).get_context_data()
|
||||
event = models.Event.objects.get(id=self.kwargs['event'])
|
||||
context['event'] = event
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if request.POST.get('rotate') and request.user.has_perm('events.change_photo'):
|
||||
photo = models.Photo.objects.get(pk=kwargs['pk'])
|
||||
photo.rotate(request.POST['rotate'])
|
||||
# return redirect(photo.get_absolute_url())
|
||||
return self.get(request)
|
||||
else:
|
||||
return generic.UpdateView.post(self, request, *args, **kwargs)
|
||||
|
||||
|
||||
class EventPhotoList(generic.ListView):
|
||||
context_object_name = 'photo_list'
|
||||
paginate_by = 36
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = generic.ListView.get_context_data(self, **kwargs)
|
||||
context['event'] = self.event
|
||||
context['form'] = forms.PhotoUploadForm(initial={'event': self.event, 'photographer': self.request.user})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
self.event = models.Event.objects.get(id=self.kwargs['event'])
|
||||
return models.Photo.objects.filter(event=self.event)
|
||||
except models.Event.DoesNotExist:
|
||||
raise Http404(_('Event does not exist'))
|
||||
|
||||
|
||||
class EventPhotoUpload(generic.FormView):
|
||||
form_class = forms.PhotoUploadForm
|
||||
template_name = 'events/photo_upload.html'
|
||||
|
||||
@method_decorator(permission_required('events.add_photo'))
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(EventPhotoUpload, self).dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = generic.FormView.get_context_data(self, **kwargs)
|
||||
context['event_list'] = models.Event.objects.archive()[:12]
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
"""
|
||||
Set the current logged in user a default value for the photographer.
|
||||
"""
|
||||
return {
|
||||
'photographer': self.request.user,
|
||||
}
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
"""
|
||||
|
||||
"""
|
||||
self.event = models.Event.objects.get(id=self.request.REQUEST.get('event'))
|
||||
photographer = self.request.POST.get('photographer', self.request.user.id)
|
||||
photographer = get_user_model().objects.get(id=photographer)
|
||||
self.counter = 1
|
||||
for upload in self.request.FILES.getlist('upload'):
|
||||
name = upload.name
|
||||
created_date, description = self.read_exif(upload)
|
||||
photo = models.Photo(
|
||||
event=self.event,
|
||||
photographer=photographer,
|
||||
image=upload,
|
||||
name=name,
|
||||
created_date=created_date,
|
||||
description=description
|
||||
)
|
||||
photo.save()
|
||||
self.counter += 1
|
||||
return redirect('event-photo-list', event=self.event.id)
|
||||
|
||||
def read_exif(self, photo):
|
||||
exif_data = pyexiv2.ImageMetadata.from_buffer(photo.read())
|
||||
exif_data.read()
|
||||
|
||||
try:
|
||||
created_date = exif_data['Exif.Image.DateTime'].value
|
||||
except:
|
||||
created_date = self.event.start + timedelta(minutes=self.counter)
|
||||
try:
|
||||
description = exif_data['Exif.Image.ImageDescription'].value
|
||||
except:
|
||||
description = ''
|
||||
return created_date, description
|
||||
|
||||
|
||||
class UpcomingEvents(generic.ListView):
|
||||
queryset = models.Event.objects.upcoming(limit=None)
|
||||
paginate_by = 16
|
||||
Reference in New Issue
Block a user